Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

static_assert(std::is_same<void, // Default

detected_or<void, foo_type, C1, char>>::value, "Unexpected");

static_assert(std::is_same<int, detected_or<void, foo_type, C2, char>>::value, "Unexpected");

Section 103.5: Overload resolution with a large number of options

If you need to select between several options, enabling just one via enable_if<> can be quite cumbersome, since several conditions needs to be negated too.

The ordering between overloads can instead be selected using inheritance, i.e. tag dispatch.

Instead of testing for the thing that needs to be well-formed, and also testing the negation of all the other versions conditions, we instead test just for what we need, preferably in a decltype in a trailing return.

This might leave several option well formed, we di erentiate between those using 'tags', similar to iterator-trait tags (random_access_tag et al). This works because a direct match is better that a base class, which is better that a base class of a base class, etc.

#include <algorithm> #include <iterator>

namespace detail

{

//this gives us infinite types, that inherit from each other template<std::size_t N>

struct pick : pick<N-1> {}; template<>

struct pick<0> {};

//the overload we want to be preferred have a higher N in pick<N>

//this is the first helper template function

template<typename T>

auto stable_sort(T& t, pick<2>)

-> decltype( t.stable_sort(), void() )

{

// if the container have a member stable_sort, use that t.stable_sort();

}

// this helper will be second best match template<typename T>

auto stable_sort(T& t, pick<1>)

-> decltype( t.sort(), void() )

{

//if the container have a member sort, but no member stable_sort

//it's customary that the sort member is stable

t.sort();

}

// this helper will be picked last template<typename T>

auto stable_sort(T& t, pick<0>)

-> decltype( std::stable_sort(std::begin(t), std::end(t)), void() )

{

// the container have neither a member sort, nor member stable_sort std::stable_sort(std::begin(t), std::end(t));

}

GoalKicker.com – C++ Notes for Professionals

518

}

//this is the function the user calls. it will dispatch the call

//to the correct implementation with the help of 'tags'. template<typename T>

void stable_sort(T& t)

{

//use an N that is higher that any used above.

//this will pick the highest overload that is well formed. detail::stable_sort(t, detail::pick<10>{});

}

There are other methods commonly used to di erentiate between overloads, such as exact match being better than conversion, being better than ellipsis.

However, tag-dispatch can extend to any number of choices, and is a bit more clear in intent.

Section 103.6: trailing decltype in function templates

Version ≥ C++11

One of constraining function is to use trailing decltype to specify the return type:

namespace details { using std::to_string;

//this one is constrained on being able to call to_string(T) template <class T>

auto convert_to_string(T const& val, int ) -> decltype(to_string(val))

{

return to_string(val);

}

//this one is unconstrained, but less preferred due to the ellipsis argument template <class T>

std::string convert_to_string(T const& val, ... )

{

std::ostringstream oss; oss << val;

return oss.str();

}

}

template <class T>

std::string convert_to_string(T const& val)

{

return details::convert_to_string(val, 0);

}

If I call convert_to_string() with an argument with which I can invoke to_string(), then I have two viable functions for details::convert_to_string(). The first is preferred since the conversion from 0 to int is a better implicit conversion sequence than the conversion from 0 to ...

If I call convert_to_string() with an argument from which I cannot invoke to_string(), then the first function template instantiation leads to substitution failure (there is no decltype(to_string(val))). As a result, that candidate is removed from the overload set. The second function template is unconstrained, so it is selected and we instead go through operator<<(std::ostream&, T). If that one is undefined, then we have a hard compile error with a template stack on the line oss << val.

GoalKicker.com – C++ Notes for Professionals

519

Section 103.7: enable_if_all / enable_if_any

Version ≥ C++11

Motivational example

When you have a variadic template pack in the template parameters list, like in the following code snippet:

template<typename ...Args> void func(Args &&...args) { //... };

The standard library (prior to C++17) o ers no direct way to write enable_if to impose SFINAE constraints on all of the parameters in Args or any of the parameters in Args. C++17 o ers std::conjunction and std::disjunction which solve this problem. For example:

///C++17: SFINAE constraints on all of the parameters in Args. template<typename ...Args,

std::enable_if_t<std::conjunction_v<custom_conditions_v<Args>...>>* = nullptr> void func(Args &&...args) { //... };

///C++17: SFINAE constraints on any of the parameters in Args.

template<typename ...Args, std::enable_if_t<std::disjunction_v<custom_conditions_v<Args>...>>* = nullptr>

void func(Args &&...args) { //... };

If you do not have C++17 available, there are several solutions to achieve these. One of them is to use a base-case class and partial specializations, as demonstrated in answers of this question.

Alternatively, one may also implement by hand the behavior of std::conjunction and std::disjunction in a rather straight-forward way. In the following example I'll demonstrate the implementations and combine them with std::enable_if to produce two alias: enable_if_all and enable_if_any, which do exactly what they are supposed to semantically. This may provide a more scalable solution.

Implementation of enable_if_all and enable_if_any

First let's emulate std::conjunction and std::disjunction using customized seq_and and seq_or respectively:

///Helper for prior to C++14. template<bool B, class T, class F >

using conditional_t = typename std::conditional<B,T,F>::type;

///Emulate C++17 std::conjunction.

template<bool...> struct seq_or: std::false_type {}; template<bool...> struct seq_and: std::true_type {};

template<bool B1, bool... Bs> struct seq_or<B1,Bs...>:

conditional_t<B1,std::true_type,seq_or<Bs...>> {};

template<bool B1, bool... Bs> struct seq_and<B1,Bs...>:

conditional_t<B1,seq_and<Bs...>,std::false_type> {};

Then the implementation is quite straight-forward:

template<bool... Bs>

using enable_if_any = std::enable_if<seq_or<Bs...>::value>;

template<bool... Bs>

GoalKicker.com – C++ Notes for Professionals

520

using enable_if_all = std::enable_if<seq_and<Bs...>::value>;

Eventually some helpers:

template<bool... Bs>

using enable_if_any_t = typename enable_if_any<Bs...>::type;

template<bool... Bs>

using enable_if_all_t = typename enable_if_all<Bs...>::type;

Usage

The usage is also straight-forward:

///SFINAE constraints on all of the parameters in Args. template<typename ...Args,

enable_if_all_t<custom_conditions_v<Args>...>* = nullptr> void func(Args &&...args) { //... };

///SFINAE constraints on any of the parameters in Args.

template<typename ...Args, enable_if_any_t<custom_conditions_v<Args>...>* = nullptr>

void func(Args &&...args) { //... };

GoalKicker.com – C++ Notes for Professionals

521