Variadic template that determines the best conversion - c++

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).

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.

Related

a concept to check if at least one of the type in a list of types can be constructed from a given type

I have a list of types TypeList. I am currently using a tuple to hold these types.
What I want is the following:
Given a new type X - check if at least one of the type from TypeList can be constructed from X. Also I want to use existing concept std::constructible_from
I started with the following concepts definitions:
template <typename From, typename To, std::size_t... Is>
concept ConstructibleFromHelper = requires(From from, To to) {
requires (std::constructible_from<typename std::tuple_element<Is, To>::type,From> || ...);
};
template <typename From, typename To>
concept ConstructibleFrom = requires {
requires ConstructibleFromHelper<From,To,std::make_index_sequence<std::tuple_size_v<To>>()>;
};
But I am getting the following error:
./type_traits.h:36:46: error: template argument for non-type template parameter is treated as function type 'std::make_index_sequence<std::tuple_size_v<To>> ()' (aka '__make_integer_seq<std::integer_sequence, unsigned long, std::tuple_size_v<To>> ()')
requires ConstructibleFromHelper<From,To,std::make_index_sequence<std::tuple_size_v<To>>()>;
Looks, like partial specialization of ConstructibleFromHelper is required to expand make_index_sequence to std::size_t... Is. But concept does not support partial specialisation.
How to make this work with tuple typelist and std::constructible_from.
If this does not work, please suggest any other method to achieve the same result.
Thanks.
failed attempt
You can do this with an immediately-invoked lambda, i.e. by expanding the tuple's elements directly in the function body
template <typename From, typename To>
concept ConstructibleFrom = []<std::size_t... Is>(std::index_sequence<Is...>) {
return (std::constructible_from<std::tuple_element_t<Is, To>, From> || ...);
}(std::make_index_sequence<std::tuple_size_v<To>>());
Demo

Why is the concept in template template argument not verified?

C++20 allows the program to specify concept for template template argument. For example,
#include <concepts>
template <typename T> concept Char = std::same_as<T, char>;
template <typename> struct S {};
template <template <Char U> typename T, typename U> T<U> foo() { return {}; }
int main() { foo<S, int>(); }
the first template argument of the function foo is expected to be a single argument template.
The concept Char is defined to be true only the type char, so an attempt to satisfy it for int shall fail. Still above program is accepted by all compilers: https://gcc.godbolt.org/z/PaeETh6GP
Could you please explain, why the concept in template template argument can be specified, but it will be still ignored?
A template (actual) argument matches a template (formal) parameter if the latter is at least as specialised as the former.
template <Char> typename T is more specialised than template <typename> struct S. Roughly speaking, template <Char> accepts a subset of what template <typename> accepts (the exact definition of what "at least as specialised" actually means is rather involved, but this is a zeroth approximation).
This means that the actual argument can be used in all contexts where the formal parameter can be used. That is, for any type K for which T<K> is valid, S<K> is also valid (because S<K> is valid for any K).
So it is OK to substitute S for T.
If you do it the other way around:
template<typename T> concept Any = true;
template<typename T> concept Char = Any<T> && std::same_as<T, char>;
template<template<Any> class T> void foo();
template<Char> struct S { };
int main()
{
foo<S>();
}
then this is ill-formed, because (roughly) you can say T<int> but not S<int>. So S is not a valid substitute for T.
Notes:
Why would we need this always-true concept Any? What's wrong with simply saying template <template <typename> typename>? Well, that's because of a special rule: if the parameter is not constrained at all, constraints of the argument are ignored, everything goes.
Why write Any<T> && std::same_as<T, char>;? To illustrate a point. The actual rules do not evaluate the boolean values of constraints, but compare constraints as formulae where atomic constraints serve as variables, see here. So the formal reason is that S has a conjunction of a strictly larger (inclusion-wise) set of atomic constraints than T. If S had the same or a strictly smaller set, it would be well-formed. If two sets are not ordered by inclusion, then neither template is more specialised, and there is no match.

Template parameter pack of mixed templated types (std::function recreation with optional arguments)

I'm attempting to make an std::function alternative that supports optional arguments with defaults. I tried a few different syntactical ideas, but the most realistic seems to be a parameter pack of specialised templates that hold argument data. Here's my desired outer syntax:
Function<
void /*Return type*/,
Arg<false, int> /*Required int parameter*/,
Arg<true, bool, true> /*Optional bool parameter that defaults to true*/
> func;
I would have liked to maintain the Function<ReturnType(args)> syntax but it appears you can only put typenames in parentheses and not classes. Here's my current code:
template<bool Optional, typename Type, Type Default>
class Arg;
template<typename Type, Type Default>
class Arg<false, Type, Default> {};
template<typename Type, Type Default>
class Arg<true, Type, Default> {};
Problem 1: I can't find a way to make the "Default" parameter non-required for the false specialisation. I've tried proxy classes with a default constructor, changing the third argument to a pointer and with specifying nullptr (which isn't really ideal syntactically), and a const reference to a type (which still requires three arguments from the user side) but nothing seems to allow two arguments to be accepted with Function<false, Type>.
Problem 2: I can't find the right syntax to get a parameter pack of mixed template argument types. I've tried (obviously invalid syntax)
template<typename RetType, Arg<bool, typename Type, Type>... args>
class Function{};
and even a double/nested template but I can't make this work either.
All of this indirection really steams from the fact that you can't have multiple parameter packs in a class template so I have to find creative ways to specify optional arguments, but I will take any solution I can get to somehow making this function at compile-time and not having to construct these functions dynamically.
I'm using C++20.
To make the third template argument optional when the first argument is false, you can use a default argument with a std::enable_if:
template <bool Optional, typename T,
T Default = std::enable_if_t<!Optional, T>{}>
class Arg;
This way, Arg<false, int> is equivalent to Arg<false, int, 0>, whereas Arg<true, int> is ill-formed.
You can use generic arguments:
template <typename R, typename... Args>
class Function {
static_assert(std::conjunction_v<is_arg_v<Args>...>);
};
Where is_arg can be something as simple as
template <typename T>
struct is_arg :std::false_type {};
template <bool Optional, typename T, T Default>
struct is_arg<Arg<Optional, T, Default>> :std::true_type {};
template <typename T>
inline constexpr bool is_arg_v = is_arg<T>::value;

Understanding how the function traits template works. In particular, what is the deal with the pointer to member function

I am trying to understand this code and I have found some more SO content on this topic.
In compact form:
#include <tuple>
namespace sqlite {
namespace utility {
template<typename> struct function_traits;
template <typename Function>
struct function_traits : public function_traits<
decltype(&Function::operator())
> { };
template <
typename ClassType,
typename ReturnType,
typename... Arguments
>
struct function_traits<
ReturnType(ClassType::*)(Arguments...) const
> {
typedef ReturnType result_type;
template <std::size_t Index>
using argument = typename std::tuple_element<
Index,
std::tuple<Arguments...>
>::type;
static const std::size_t arity = sizeof...(Arguments);
};
}
}
This led me to learn about the pointer-to-member-function feature of the language, and it's pretty clear to me at this point how to use the function traits (it's fairly straightforward that I can pull out not only the return type but the arity and even the types of the arguments) but I have gotten stuck in understanding why this works, or indeed why it is even possible for it to work...
Let's break everything down piece by piece.
template<typename> struct function_traits;
That is the primary template declaration, declaring a template class with a single template parameter.
template <typename Function>
struct function_traits : public function_traits<
decltype(&Function::operator())
> { };
That is essentially a forwarding trait to allow functors (objects which have an operator()) to be used with this traits class. It's a definition of the primary template which inherits from the traits for operator(). This version will be selected when the specialization which is defined later is not matched.
template <
typename ClassType,
typename ReturnType,
typename... Arguments
>
struct function_traits<
ReturnType(ClassType::*)(Arguments...) const
>
That begins the definition of a partial specialization of function_traits for pointers to member functions. When you pass a member function pointer type as the template argument to function_traits, this specialization will be selected. The return type, class type and argument types will be deduced as template parameters.
typedef ReturnType result_type;
That is a simple typedef which aliases the deduced ReturnType template parameter to result_type.
template <std::size_t Index>
using argument = typename std::tuple_element<
Index,
std::tuple<Arguments...>
>::type;
That is what is called an alias template. When you provide an index such as function_traits<myFunc>::template argument<2>, it will collapse down to typename std::tuple_element<2, std::tuple<Arguments...>>::type. The goal of that is to extract the type of the argument at index Index. Rather than writing out all the template metaprogramming code to do that manually, the author chose to use the existing code in std::tuple. std::tuple_element extracts the type of the nth element of a tuple.
static const std::size_t arity = sizeof...(Arguments);
sizeof... is used to get the size of a variadic template parameter pack, so this line stores the number of arguments for the function in the static integral member arity.
#tartanllama 's answer actually forgets to add std::remove_reference from the code example, which is necessary to cover all cases.
The template general case that handles lambdas should really be:
template <typename Function>
struct function_traits : public function_traits<
decltype(&std::remove_reference_t<Function>::operator())
> { };
If you're looking for a complete solution for all types in c++ that can be invoked, see my answer on a different SO question.
I did realize that the part including ClassType::* is meant to bind to decltype(&Function::operator()). I was learning about this construct (pointer to member function), but really having a tough time understanding why it's important for it to exist.
ReturnType(ClassType::*)(Arguments...) const is a template typename, in this case it is to be matched to a pointer to member function, and it is cleverly constructed in such a way as to be bound to the operator() member function, allowing the pattern matching to pull out the relevant types for us, those of the args and the return type. The ClassType would presumably become recognized as the same thing as the Function template parameter.
I was also confused by why a funny-looking templated using statement is required to look up the argument type values. I then realized that the Arguments... is a variadic template type construct which has to be tuple-ized using compile-time features and the whole business has nothing to do with "real" tuples. When I looked up tuple_element indeed it is intended as a tool for manipulating types at compile-time.
the remainder of this answer I am not sure about
I do wonder why pointer-to-member-function was used. I guess the reason could be that a regular function pointer or some such construct cannot actually be used to match the operator () member of functors, as a member function requires an instance of the object in order to become callable. This answer sheds some more light on this subject. I mostly understand it now I think.

How do I enable_if a struct depending on a condition and how many arguments?

I want to create a meta function that returns a certain type if more than 1 argument is passed to it, and another type based on a condition if only a single argument is passed to it. The condition is arbitrary so it will require an enable_if or something of that kind, but for this example I'll just make it a type comparison. Let's simplify it to the following
If a single argument is passed and that argument is an int, return bool
If a single argument is passed and that argument is a double, return int
If more than 1 argument is passed, return double
To achieve this I have tried to do the following:
#include <type_traits>
template <typename Enable, typename...Args>
struct Get;
// multiple arguments; return double regardless of the condition
template <typename FirstArg, typename... OtherArgs>
struct Get<typename std::enable_if<true>::type, FirstArg, OtherArgs...>
{
using type = double;
};
// single int; return bool
template <typename Arg>
struct Get<typename std::enable_if<std::is_same<Arg, int>::value>::type, Arg>
{
using type = double;
};
// single double; return int
template <typename Arg>
struct Get<typename std::enable_if<std::is_same<Arg, double>::value>::type, Arg>
{
using type = int;
};
int main()
{
static_assert(std::is_same<typename Get<double>::type, int>::value, "");
static_assert(std::is_same<typename Get<int>::type, bool>::value, "");
static_assert(std::is_same<typename Get<bool, int>::type, double>::value, "");
return 0;
}
Output:
prog.cpp: In function ‘int main()’:
prog.cpp:29:51: error: ‘type’ in ‘struct Get<double>’ does not name a type
static_assert(std::is_same<typename Get<double>::type, int>::value, "");
^
prog.cpp:29:60: error: template argument 1 is invalid
static_assert(std::is_same<typename Get<double>::type, int>::value, "");
I would be grateful for an answer that teaches me why this doesn't work the way I expected, not just how to fix it. I'm struggling to find good resources on template meta-programming and have so far been programming rather haphazardly, which is something I'd very much like to fix!
Live example here
The template arguments to a specialization are not the template arguments to the original definition.
The ones that correspond to the original definition are after the template name.
The arguments in the template<> part of the specialization are the types introduced to match the specialization.
So original definition:
template <typename Enable, typename...Args>
struct Get;
1 or more type arguments. And, unless a specialization matches, not defined.
First specialization:
template <typename FirstArg, typename... OtherArgs>
struct Get<typename std::enable_if<true>::type, FirstArg, OtherArgs...> {
using type = double;
};
well, std::enable_if<true>::type is just void, so this is the same as:
template <typename FirstArg, typename... OtherArgs>
struct Get<void, FirstArg, OtherArgs...> {
using type = double;
};
So this matches if the first type is void, and there is at least one other type.
Second specialization:
template <typename Arg>
struct Get<typename std::enable_if<std::is_same<Arg, int>::value>::type, Arg> {
using type = double;
};
So this tries to match if there are two types. The second one is pattern matched.
If it isn't int, the first one SFINAE fails. If it is an int, the first one ends up being void. So this matches Get<void, int> only.
Third specialization:
template <typename Arg>
struct Get<typename std::enable_if<std::is_same<Arg, double>::value>::type, Arg> {
using type = int;
};
similarly, this matches Get<void, double>.
Specialization is pattern matching. SFINAE enable_if clauses cannot be pattern matched. So the pattern match runs, then the enable_if clauses are evaluated. If they fail, the specialization does not match. If they succeed, the enable_if clause produces a type. After all types are generated (pattern and not), the resulting list of types either matches or does not.
The easy ways to use this machinery include having your public version forward to a details one, passing void as the first type, and doing your enable_if work there.
Another way is to bundle your types up into a list of types, like template<class...>struct types{};, and pass that as one argument, and void as the second argument, and do SFINAE again on that void.
Here is an example:
namespace details {
template<class...>struct types{};
template<class Types, class=void>
struct foo;
template<class T0, class... Ts>
struct foo<types<T0, Ts...>,typename std::enable_if<
std::is_same<T0, int>::value && (sizeof...(Ts)>=1)
>> {
using type=double;
};
}
template<class T0, class... Ts>
struct foo:details::foo< details::types<T0, Ts...> >{};
The specialization of details::foo pattern matches the types bundle. It does the enable_if logic with those pattern matched types. The enable_if passes if and only if the first type is an int and there are 1 or more additional types (for an arbitrary set of tests).