Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic template that determines the best conversion

How can we implement a variadic template that, given a type T and a list of types E1, E2, ... EN, determines the type of that list for which the conversion from T to that type is, according to overload resolution, the best?

void should be the output if no best conversion exists - in other words, when either there is an ambiguity or T cannot be converted to any type in the list.
Note that this implies that our template should be SFINAE-friendly, i.e. not hard-error when no best conversion exists.

The following static_asserts should succeed:

static_assert( std::is_same< best<int, long, short>,       void >{}, "" );
static_assert( std::is_same< best<int, long, std::string>, long >{}, "" );
static_assert( std::is_same< best<int>,                    void >{}, "" );

(Assuming, for simplicity, that best is an alias template referring to the actual template)

This case is left unspecified:

static_assert( std::is_same< best<int, int, int>, ???>{}, "" );

Either void or int should be acceptable here. (If the latter is chosen then we can still check in a wrapper template whether the result type is contained twice in the list, and if it is, output void instead).

like image 320
Columbo Avatar asked Dec 07 '14 01:12

Columbo


People also ask

What is the use of Variadic templates?

With the variadic templates feature, you can define class or function templates that have any number (including zero) of parameters. To achieve this goal, this feature introduces a kind of parameter called parameter pack to represent a list of zero or more parameters for templates.

What are Variadic templates C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

What is parameter pack in c++?

Parameter packs (C++11) A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.

What is Pack expansion?

Pack expansion A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order. template<class...

What are variadic templates in C++?

Variadic templates in C++. Finally, there's a way to write functions that take an arbitrary number of arguments in a type-safe way and have all the argument handling logic resolved at compile-time, rather than run-time. Variadic templates can be used for much more than just functions that take an arbitrary number of arguments;

What is a variadic template pack in Python?

The pack can have arbitarary number of parameters having different types. At the end of compile-time, a variadic template function is translated into multiple solid functions that each of them accepts a certain number of parameters with certain types. There is no explicit for-loop commands to iterate the pack parameters.

Should I be worried about the performance of variadic templates?

If you're concerned with the performance of code that relies on variadic templates, worry not. As there's no actual recursion involved, all we have is a sequence of function calls pre-generated at compile-time. This sequence is, in practice, fairly short (variadic calls with more than 5-6 arguments are rare).

What is pack expansion in variadic templates?

The variadic template feature also introduces pack expansion to indicate that a parameter pack is expanded. Two existing techniques, template argument deduction and partial specialization , can also apply to templates that have parameter packs in their parameter lists. A parameter pack can be a type of parameter for templates.


1 Answers

My currently best approach:

#include <type_traits>

template <class T> using eval = typename T::type;

template <class T> struct identity {using type = T;};

template <typename T, typename... E>
class best_conversion
{
    template <typename...> struct overloads {};

    template <typename U, typename... Rest>
    struct overloads<U, Rest...> :
        overloads<Rest...>
    {
        using overloads<Rest...>::call;

        static identity<U> call(U);
    };

    template <typename U>
    struct overloads<U>
    {
        static identity<U> call(U);
    };

    template <typename... E_>
    static identity<eval<decltype(overloads<E_...>::call(std::declval<T>()))>>
    best_conv(int);

    template <typename...>
    static identity<void> best_conv(...);

public:

    using type = eval<decltype(best_conv<E...>(0))>;
};

template <typename... T>
using best_conversion_t = eval<best_conversion<T...>>;

Demo. For the "unspecified" case above this template will give you int.

The basic idea is to put a bunch of overloaded functions with one parameter into different scopes that name lookup will look in, with the parameter and return type of each overload corresponding to one of the types in our list.
overloads does this job by recursively introducing one declaration of call at a time and adapting all previously introduced calls from the base specializations via using declarations. That way all calls are in different scopes but are considered equally when it comes to overload resolution.

Then apply SFINAE in a function template best_conv to check whether the call to call (inside overloads) is well-formed: If it is, take the return type (which is by definition the parameter type) of the selected declaration and use it as our result - it will be the type we are looking for.
Also provide a second overload of best_conv that returns void and can be selected as a default (when SFINAE applies in the first overload and kicks it out of the candidate set).

The return types use identity<> to avoid type decays when working with e.g. array or function pointer types.

like image 52
Columbo Avatar answered Oct 14 '22 14:10

Columbo