Ex W9

constexpr Operator for Arithmetic and Comparisons of std::ratio

There is a type in the standard library for representing ratios (std::ratio): http://en.cppreference.com/w/cpp/numeric/ratio/ratio

Implement the normal arithmetic operators for values of std::ratio<N, D> types and return the correct resulting type using std::ratio_add<> etc.

Do the same for the comparison operators std::ratio_less<R1, R2> etc.

Extensions

Hint

You can use the following definition of is_ratio:

template <typename R>
constexpr bool is_ratio{false};
template <intmax_t N, intmax_t D>
constexpr bool is_ratio<std::ratio<N,D>>{true};

McCarthy 91 Compile Time Function

The McCarthy 91 function is a recursive function for formal verification (https://en.wikipedia.org/wiki/McCarthy_91_function). We will just implement it for exercising compile-time constructs in C++.

Implement the following versions of the function (of course write tests first):

SFINAE for Feature Detection (Tutorial)

Your goal is to provide a versatile print function that either uses the << operator of the type to print the value or to report a meaningful message. But in both cases the code shall compile.

Example:

struct X{};

int main(){
  printer(std::cout, 42); //prints: 42
  printer(std::cout, X{}); //prints: cannot print X
  printer(std::cout, std::cout); //prints: cannot print std::ostream
}

Some Infrastructure

Detecting a specific feature like the existence of the output operator (<<) requires some infrastructure. A proposal for the C++ standard (N4502 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf, http://en.cppreference.com/w/cpp/experimental/is_detected) suggests the implementation of the detect template. This template looks similar to the following:

template<typename, template<typename > class, typename = void_t<>>
struct detect : std::false_type {
};

template<typename T, template<typename > class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

The template detect is derived from std::false_type and empty otherwise. It takes three template paramters:

The default type, the template void_t is a C++17 meta-programming feature for consuming types. It acts as a sink for its arguments not further processing them. However, the parameters are still substituted with the arguments and can therefore provoke substitution failures. In this way specializations can be eliminated using SFINAE.

The specialized detect template is derived from std::true_type. It tries to instanciate the given template (second template parameter) with the given type (first parameter). Since the result of this application is not important that application happens inside the third template argument, which is void_t. This if T can be successfully used as template argument for the template template parameter (Op) this specialization is selected. This results in the specific detect instance being derived from std::true_type. Otherwise, if a substition failure happens, the base template is detected, resulting in detect being derived from std::false_type.

The std::void_t template could be implemented as follows. Use this definition if the standard library of your compiler does not provide it:

// used to swallow up superflous template arguments but check their validity
template<typename ...>
using void_t = void; // in std:: in C++17

With the infrastructure above you are able to specify predicates and apply them to types.

Resolution Step by Step

has_ostream_shift_t

First, create predicate to check whether your type can be "outputted" to std::ostream. Implement a template alias has_ostream_shift_t. This alias template will be the core for triggering SFINAE. The aliased type can be determined by decltype(). The effective type is not important, the primary goal is to determine whether decltype can determine the type of an expression using the output operator (<<) on the corresponding arguments. Let's examine a hypothetical expression:

out << v

out is of type std::ostream & and v is of an arbitrary type.

There are two possible outcomes:

The problem here is getting such hypothetical variables. You might just create an instance using T{}. But this does neithr work for types that don't have a default constructor nor can you create a reference (as you would need to std::ostream &). There exists a library feature to overcome this issue. The std::declval() template function materializes a value of the given type from nothing. As this feature does not sound like something possibly done in reality, it can only be used in unevaluated contexts. However, this is enough for our application in decltype, as the given expression is not evaluated.

With this information try to specify the has_ostream_shift_t alias template. If you are stuck on this task, see SolW9.

is_outputtable

Second, create an alias template for is_outputtable. The alias should be either for std::true_type or std::false_type depending on whether the given type template parameter is outputtable or not.

Use the detect and has_ostream_shift_t templates to specify it.

printer

Putting it all together. Specify the function template printer with two overloads:

Remaining fibo-List Compile Time Exercises

Implement computing the nth fibonacci number in the following means as compile-time computation

Do the same variations (except the first) returning an array of size n of the n first fibonacci numbers instead (0,1,1,2,3,...). Use the std::array clone from the lecture in this week (ar) providing constexpr mutating member functions.

Last edited April 26, 2017