C++ SFINAE partial specialization - c++

I've been trying to define an auxiliary class to help me work with template methods in which I would like a generic implementation both for complex and real types.
That has been my attempt so far:
#include<type_traits>
#include<complex>
template<class T>
struct is_complex{ static constexpr bool value = false;};
template<class T>
struct is_complex<std::complex<T>> :
std::integral_constant<bool,
std::is_integral<T>::value ||
std::is_floating_point<T>::value>{};
template<class T>
struct is_arithmetic:
std::integral_constant<bool,
std::is_integral<T>::value ||
std::is_floating_point<T>::value ||
is_complex<T>::value>{};
template<class T,
typename std::enable_if_t<is_arithmetic<T>::value,int> =0>
struct real_type {typedef T type;};
template<class T>
struct real_type<typename std::complex<T>>{typedef T type;};
I want to get something like
typename real_type<std::complex<double>> myVar1;//myVar1 is double
typename real_type<double> myVar2;//myVar2 is double
I was able to make it work as long as I didn't care that non-arithmetic types also had real_type<T>::type. But now that I have added this additional constraint, I cannot make it work and I don't really see why.
To clarify: I would like that calls like real_type<std::string>::type would generate compile-time errors. I want these calls to be valid only to arithmetic (including complex) and integral types.
The compiler error of my latest attempt was:
non-type template argument specializes a template parameter with dependent type 'typename std::enable_if_t<is_arithmetic<T>::value, int>' (aka 'typename enable_if<is_arithmetic<T>::value, int>::type')
But I don't know how to deal with it. If this information is useful, I have access to compilers supporting C++17.

Usually this is done with specialization and a template defaulted parameter.
I mean
template <typename, typename = void>
struct real_type;
template <typename T>
struct real_type<T, std::enable_if_t<std::is_arithmetic_v<T>>>
{ using type = T; };
template <typename T>
struct real_type<std::complex<T>, void>
{ using type = T; };
where you have a separate specialization for std::complex and, as observed by Patrick Roberts (thanks), without std::complex your is_arithmetic become a duplicate of std::is_arithmetic (so is better directly use std::is_arithmetic).
You get
real_type<int> r1; // compile
real_type<std::complex<float>> r2; // compile
//real_type<std::string> r3; // compilation error

May I suggest a slightly more concise implementation that works more generally (which requires c++17):
#include <type_traits>
// catch all for single parameters that have 0 template parameters
template <typename T>
struct real_type{
static_assert(std::is_arithmetic_v<T>);
using type = T;
};
template <typename T>
using real_type_t = typename real_type<T>::type;
// magically catch anything which matches V<T, Ts...> and exposes T as `type`
template <template <typename...> typename V, typename T, typename...Ts>
struct real_type<V<T, Ts...>>{
using type = real_type_t<T>;
};
#include <vector>
#include <complex>
using d = real_type_t<double>;
static_assert(std::is_same_v<d, double>);
using d2 = real_type_t<std::vector<double>>;
static_assert(std::is_same_v<d2, double>);
using d3 = real_type_t<std::complex<double>>;
static_assert(std::is_same_v<d3, double>);
// doesn't compile
struct NotValid {};
using d4 = real_type_t<std::vector<NotValid>>;

Related

Using std::conditional with is_class<int>, getting compile error

I want to have a type which depends on some other class X having X::value_type.
If there is no such typedef inside the other class, I want to just use X by itself.
So I am looking for code like:
TypeChecker<X>::value_type // Here, value_type might be X or X::value_type, depending on X.
But my initial attempts are failing, see below:
Here's a program that does not compile:
#include<type_traits>
template<typename T>
struct TypeChecker {
typedef typename std::conditional<
std::is_class<T>::value,
typename T::value_type, // I believe the error is due to this line (could be wrong)
T>::type value_type;
};
int main()
{
TypeChecker<int>::value_type x = 3;
return 0;
}
It gives: error: 'int' is not a class, struct, or union type
You can try online: godbolt link
I'm confused, because I thought std::conditional would choose the right branch, but it seems to be evaluating both of them, in some sense. At least, enough to cause a compile error.
Help?
The problem is T::value_type is passed to std::conditional as the template argument, when T is int the expression itself is invalid.
You can do it with class template and partial specialization instead.
template<typename T, typename = void>
struct TypeChecker {
typedef T value_type;
};
template<typename T>
struct TypeChecker<T, typename std::enable_if<std::is_class<T>::value>::type> {
typedef typename T::value_type value_type;
};
BTW: std::is_class seems to be much loose condition; you can constrain it as has the member type value_type, e.g.
template<typename T, typename = void>
struct TypeChecker {
typedef T value_type;
};
template<typename T>
struct TypeChecker<T, std::void_t<typename T::value_type>> {
typedef typename T::value_type value_type;
};
std::conditional does not "short-circuit". For std::conditional<C,X,Y> to even be instantiated as a class type, the three template arguments must all be actual types. There's no way to have a template that can be used with one of its template parameters representing an illegal or unknown type, even if that parameter doesn't directly matter.
Some solutions to your TypeChecker:
// C++17, using partial specialization SFINAE:
template <typename T, typename Enable = void>
struct TypeChecker {
using value_type = T;
};
template <typename T>
struct TypeChecker<T, std::void_t<typename T::value_type>> {
using value_type = typename T::value_type;
};
// C++11 or later, using function SFINAE:
template <typename T>
struct type_identity { using type = T; };
template <typename T>
type_identity<typename T::value_type> TypeChecker_helper(int); // not defined
template <typename T>
type_identity<T> TypeChecker_helper(...); // not defined
template <typename T>
struct TypeChecker {
using value_type = typename decltype(TypeChecker_helper<T>(0))::type;
};
(Aside: I'm always a bit suspicious of templates that automatically "unwrap" things, like this TypeChecker. It could thwart code which specifically wants to use a container or iterator as a value directly.)

Using `void_t` to detect multiple inheritance type repetition errors

I want to implement a has_no_duplicates<...> type trait that evaluates to std::true_type if the passed variadic type list has no duplicate types.
static_assert(has_no_duplicates<int, float>{}, "");
static_assert(!has_no_duplicates<float, float>{}, "");
Let's assume, for the scope of this question, that I want to do that using multiple inheritance.
When a class inherits from the same type more than once, an error occurs.
template<class T>
struct type { };
template<class... Ts>
struct dup_helper : type<Ts>... { };
// No errors, compiles properly.
dup_helper<int, float> ok{};
// Compile-time error:
// base class 'type<float>' specified more than once as a direct base class
dup_helper<float, float> error{};
I assumed I could've used void_t to "detect" this error, but I couldn't implement a working solution following the code samples from cppreference.
This is what I tried:
template<class, class = void>
struct is_valid
: std::false_type { };
// First try:
template<class T>
struct is_valid<T, std::void_t<decltype(T{})>>
: std::true_type { };
// Second try:
template<class T>
struct is_valid<T, std::void_t<T>>
: std::true_type { };
For my third try, I tried delaying the expansion of dup_helper<...> using a wrapper class that took dup_helper as a template template parameter, like wrapper<dup_helper, ...> and expanded it inside of void_t.
Unfortunately, all my tries resulted in the aforementioned error always preventing compilation.
I assume this type of error is not detectable as a "substitution failure", but I'd like confirmation.
Is this kind of error actually impossible to detect using void_t? (Will it always result in a compilation failure?)
Is there a way to detect it without causing compilation to fail? (Or a non-void_t workaround that still makes use of the "multiple inheritance trick")?
As #Canoninos noted, the problem is that:
it isn't the declaration of dup_helper<T, T> which causes an error but its definition [...].
Or, in Standardese, the error occurs outside the "immediate context" ([temp.deduct]) of the substitution:
8 - [...] Only invalid types and expressions in the immediate context of the function type and
its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types
and expressions can result in side effects such as the instantiation of class template specializations and/or
function template specializations, the generation of implicitly-defined functions, etc. Such side effects are
not in the “immediate context” and can result in the program being ill-formed. — end note ]
Here the error occurs while instantiating dup_helper<float, float> so is not in the "immediate context".
One multiple inheritance trick that's very close to yours involves adding an extra layer of inheritance, by indexing the multiple bases:
helper<<0, 1>, <float, float>>
+
+----+----+
v v
ix<0, float> ix<1, float>
+ +
v v
t<float> t<float>
This gives us a helper class with a valid definition and that can be instantiated but not cast to its ultimate base classes, because of ambiguity:
static_cast<t<float>>(helper<...>{}); // Error, SFINAE-usable
Example.
That's my solution using meta-programming and type-list idiom.
I use this code as part of my library implementing reflection for C++. I think there is no need in void_t or inheritance at all to solve this task.
template <typename ...Args>
struct type_list
{};
using empty_list = type_list<>;
// identity
template<typename T>
struct identity
{
using type = T;
};
// is_typelist
template<typename T>
struct is_typelist: std::false_type
{};
template<typename ...Args>
struct is_typelist<type_list<Args...>>: std::true_type
{};
template<typename T>
struct check_typelist
{
using type = void;
static constexpr void *value = nullptr;
static_assert(is_typelist<T>::value, "T is not a type_list!");
};
// indexof
namespace internal {
template<typename T, typename V, std::int64_t index>
struct typelist_indexof_helper: check_typelist<T>
{};
template<typename H, typename ...T, typename V, std::int64_t index>
struct typelist_indexof_helper<type_list<H, T...>, V, index>:
std::conditional<std::is_same<H, V>::value,
std::integral_constant<std::int64_t, index>,
typelist_indexof_helper<type_list<T...>, V, index + 1>
>::type
{};
template<typename V, std::int64_t index>
struct typelist_indexof_helper<empty_list, V, index>: std::integral_constant<std::int64_t, -1>
{};
}
template<typename T, typename V>
using typelist_indexof = ::internal::typelist_indexof_helper<T, V, 0>;
template<typename T, typename V>
struct typelist_exists: std::integral_constant<bool, typelist_indexof<T, V>::value >= 0>
{};
// remove_duplicates
namespace internal {
template<typename P, typename T>
struct typelist_remove_duplicates_helper: check_typelist<T>
{};
template<typename ...P, typename H, typename ...T>
struct typelist_remove_duplicates_helper<type_list<P...>, type_list<H, T...>>:
std::conditional<typelist_exists<type_list<T...>, H>::value,
typelist_remove_duplicates_helper<type_list<P...>, type_list<T...>>,
typelist_remove_duplicates_helper<type_list<P..., H>, type_list<T...>>
>::type
{};
template<typename ...P>
struct typelist_remove_duplicates_helper<type_list<P...>, empty_list>: identity<type_list<P...>>
{};
}
template<typename T>
using typelist_remove_duplicates = ::internal::typelist_remove_duplicates_helper<empty_list, T>;
template<typename ...Args>
struct has_no_duplicates: std::integral_constant<bool, std::is_same<type_list<Args...>,
typename typelist_remove_duplicates<type_list<Args...>>::type>::value>
{};
DEMO

Enforcing invariants in metaprogramming

I'd like to be able to check invariants for classes that are used in metaprograms. My first naive approach was
template <int N>
struct digit
{
static_assert((N >= 0) && (N < 10), "bad invariant");
};
using boom = digit<99>;
However this compiles without any problems. The static assertion is triggered only when the illegal class is constructed.
It is possible when adding an additional template parameter:
#include <type_traits>
template <int N,
typename = typename std::enable_if<(N >= 0) && (N < 10)>::type>
struct digit;
using crash = digit<-7>;
When I wanted to apply this technique to a class that is used as a list of types:
#include <type_traits>
template <typename ...> struct are_integral;
template <typename T, typename ...Ts>
struct are_integral<T, Ts...>
{
static const bool value = std::is_integral<T>::value &&
are_integral<Ts...>::value;
};
template <>
struct are_integral<> : std::true_type { };
template <typename ...Ts,
typename = typename std::enable_if<are_integral<Ts...>::value>::type>
struct list;
using ok = list<int, long, char>;
using bad = list<double>;
It simply does not work since gcc complains that
error: parameter pack 'Ts' must be at the end of the template
parameter list struct list;
Even if it would work, the class is useless as the template parameter pack does not reflect the typelist.
So I tried to use an "illegal" base class:
template <typename> struct check;
template <typename ...Ts>
struct list : check<typename std::enable_if<are_integral<Ts...>::value>::type>
{ };
using ok = list<int, long, char>;
using bad = list<double>;
This compiles without problems.
Is there any way to accomplish something like that in c++11 or do I have to wait for concepts?
Your problem arises because the template is not instantiated when it is aliased, so the static_assert does not trigger.
If this is acceptable, you could add some indirection and use a builder metafunction to create your compile-time list. This metafunction would perform the check.
template <typename ...Ts>
struct make_list
{
static_assert(are_integral<Ts...>::value, "all types must be integral");
typedef list<Ts...> type;
};
using ok = make_list<int, long, char>::type;
using bad = make_list<double>::type;
Another solution is to use a dummy type to wrap your parameter pack into a first-class type.
// dummy wrapper
template <typename ...>
struct pack;
template <typename ...> struct are_integral;
// specialization for wrapped packs
template <typename ...Ts>
struct are_integral<pack<Ts...>> : are_integral<Ts...>
{
};
template <typename T, typename ...Ts>
struct are_integral<T, Ts...>
{
static const bool value = std::is_integral<T>::value &&
are_integral<Ts...>::value;
};
template <>
struct are_integral<> : std::true_type { };
// helper type which performs the check
template <typename Pack,
typename = typename std::enable_if<are_integral<Pack>::value>::type>
struct list_helper;
// actual type (alias) you will expose
template <typename ...Ts>
using list = list_helper<pack<Ts...>>;
using ok = list<int, long, char>;
using bad = list<double>; // compiler error
Using a wrapper often comes in handy when dealing with parameter packs, because it makes them more amenable to manipulation: the wrapper is a type like any other, which can be stored, appear anywhere in the parameter list, be passed to unary metafunctions, etc.

SFINAE failure with typedef in class template referring to typedef in another class template

I've been working on a way of producing compile-time information about classes that wrap other classes in C++. In a minimal example of the problem I am about to ask about, such a wrapper class:
contains a typedef WrappedType defining the type of the wrapped class; and
overloads a struct template called IsWrapper to indicate that it is a wrapper class.
There is a struct template called WrapperTraits that can then be used to determine the (non-wrapper) root type of a hierarchy of wrapped types. E.g. if the wrapper class is a class template called Wrapper<T>, the root type of Wrapper<Wrapper<int>> would be int.
In the code snippet below, I have implemented a recursive struct template called GetRootType<T> that defines a typedef RootType that gives the root type of wrapper type T. The given definition of WrapperTraits just contains the root type as defined by GetRootType, but in practice it would have some additional members. To test it, I have written an ordinary function f that takes an int, and an overloaded function template f that takes an arbitrary wrapper class that has int as its root type. I have used SFINAE to distinguish between them, by using std::enable_if in the function template's return type to check whether or not f's argument's root type is int (if f's argument is not a wrapper, attempting to determine its root type will fail). Before I ask my question, here is the code snippet:
#include <iostream>
#include <type_traits>
// Wrapper #######################################
template<class T>
struct Wrapper {typedef T WrappedType;};
template<class T, class Enable=void>
struct IsWrapper: std::false_type {};
template<class T>
struct IsWrapper<Wrapper<T> >: std::true_type {};
// WrapperTraits #######################################
template<
class T,
bool HasWrapper=
IsWrapper<T>::value && IsWrapper<typename T::WrappedType>::value>
struct GetRootType {
static_assert(IsWrapper<T>::value,"T is not a wrapper type");
typedef typename T::WrappedType RootType;
};
template<class T>
struct GetRootType<T,true> {
typedef typename GetRootType<typename T::WrappedType>::RootType RootType;
};
template<class T>
struct WrapperTraits {
typedef typename GetRootType<T>::RootType RootType;
};
// Test function #######################################
void f(int) {
std::cout<<"int"<<std::endl;
}
// #define ROOT_TYPE_ACCESSOR WrapperTraits // <-- Causes compilation error.
#define ROOT_TYPE_ACCESSOR GetRootType // <-- Compiles without error.
template<class T>
auto f(T) ->
typename std::enable_if<
std::is_same<int,typename ROOT_TYPE_ACCESSOR<T>::RootType>::value
>::type
{
typedef typename ROOT_TYPE_ACCESSOR<T>::RootType RootType;
std::cout<<"Wrapper<...<int>...>"<<std::endl;
f(RootType());
}
int main() {
f(Wrapper<int>());
return 0;
}
This compiles correctly (try it here) and produces the output:
Wrapper<...<int>...>
int
However, I have used GetRootType to determine the root type in the call to std::enable_if. If I instead use WrapperTraits to determine the root type (you can do this by changing the definition of ROOT_TYPE_ACCESSOR), GCC produces the following error:
test.cpp: In instantiation of ‘struct WrapperTraits<int>’:
test.cpp:49:6: required by substitution of ‘template<class T> typename std::enable_if<std::is_same<int, typename WrapperTraits<T>::RootType>::value>::type f(T) [with T = int]’
test.cpp:57:15: required from ‘typename std::enable_if<std::is_same<int, typename WrapperTraits<T>::RootType>::value>::type f(T) [with T = Wrapper<int>; typename std::enable_if<std::is_same<int, typename WrapperTraits<T>::RootType>::value>::type = void]’
test.cpp:62:19: required from here
test.cpp:21:39: error: ‘int’ is not a class, struct, or union type
bool HasWrapper=IsWrapper<T>::value && IsWrapper<typename T::WrappedType>::value>
My question is: what rules about argument deduction in the C++ standard explain why using WrapperTraits causes a compilation error but using GetRootType doesn't? Please note that what I want in asking this is to be able to understand why this compilation error occurs. I'm not so interested in what changes could be made to make it work, as I already know that changing the definition of WrapperTraits to this fixes the error:
template<
class T,
class Enable=typename std::enable_if<IsWrapper<T>::value>::type>
struct WrapperTraits {
typedef typename GetRootType<T>::RootType RootType;
};
template<class T>
struct WrapperTraits<T,typename std::enable_if<!IsWrapper<T>::value>::type> {
};
However, if anyone can see a more elegant way of writing f and WrapperTraits, I would be very interested in seeing it!
The first part of your question is why it fails. The answer is that on a compile-time level, && does not have short-circuit properties. This line:
bool HasWrapper=IsWrapper<T>::value && IsWrapper<typename T::WrappedType>::value>
fails because the first condition is false, yet the compiler tries to instantiate the second part, but T is int and hence T::WrappedType does not work.
To answer the second part of your question on how to make it easier, I think the following should make you quite happy:
#include <iostream>
#include <type_traits>
// Wrapper #######################################
template<class T>
struct Wrapper {};
// All you need is a way to unwrap the T, right?
template<class T>
struct Unwrap { using type = T; };
template<class T>
struct Unwrap<Wrapper<T> > : Unwrap<T> {};
// Test function #######################################
void f(int) {
std::cout<<"int"<<std::endl;
}
// Split unwrapping and checking it with enable_if<>:
template<class T,class U=typename Unwrap<T>::type>
auto f(T) ->
typename std::enable_if<
std::is_same<int,U>::value
>::type
{
std::cout<<"Wrapper<...<int>...>"<<std::endl;
f(U());
}
int main() {
f(Wrapper<int>());
return 0;
}
Live example
The problem you are having is due to the fact that SFINAE only happens in the "immediate context" (a term that the standard uses, but does not define well) of a template instantiation. The instantiation of WrapperTraits<int> is in the immediate context of the instantiation of auto f<int>() -> ..., and it succeeds. Unfortunately, WrapperTraits<int> has an ill-formed member RootType. Instantiation of that member is not in the immediate context, so SFINAE does not apply.
To get this SFINAE to work as you intend, you need to arrange for WrapperTraits<int> to not have a type named RootType, instead of having such a member but with an ill-formed definition. This is why your corrected version works as intended, although you could save some repetition by reordering:
template<class T, class Enable=void>
struct WrapperTraits {};
template<class T>
struct WrapperTraits<T,typename std::enable_if<IsWrapper<T>::value>::type> {
typedef typename GetRootType<T>::RootType RootType;
};
I would probably code up the entire traits system as (DEMO):
// Plain-vanilla implementation of void_t
template<class...> struct voider { using type = void; };
template<class...Ts>
using void_t = typename voider<Ts...>::type;
// WrapperTraits #######################################
// Wrapper types specialize WrappedType to expose the type they wrap;
// a type T is a wrapper type iff the type WrappedType<T>::type exists.
template<class> struct WrappedType {};
// GetRootType unwraps any and all layers of wrappers.
template<class T, class = void>
struct GetRootType {
using type = T; // The root type of a non-WrappedType is that type itself.
};
// The root type of a WrappedType is the root type of the type that it wraps.
template<class T>
struct GetRootType<T, void_t<typename WrappedType<T>::type>> :
GetRootType<typename WrappedType<T>::type> {};
// non-WrappedTypes have no wrapper traits.
template<class T, class = void>
struct WrapperTraits {};
// WrappedTypes have two associated types:
// * WrappedType, the type that is wrapped
// * RootType, the fully-unwrapped type inside a stack of wrappers.
template<class T>
struct WrapperTraits<T, void_t<typename WrappedType<T>::type>> {
using WrappedType = typename ::WrappedType<T>::type;
using RootType = typename GetRootType<T>::type;
};
// Convenience aliases for accessing WrapperTraits
template<class T>
using WrapperWrappedType = typename WrapperTraits<T>::WrappedType;
template<class T>
using WrapperRootType = typename WrapperTraits<T>::RootType;
// Some wrappers #######################################
// Wrapper<T> is a WrappedType
template<class> struct Wrapper {};
template<class T>
struct WrappedType<Wrapper<T>> {
using type = T;
};
// A single-element array is a WrappedType
template<class T>
struct WrappedType<T[1]> {
using type = T;
};
// A single-element tuple is a WrappedType
template<class T>
struct WrappedType<std::tuple<T>> {
using type = T;
};
Although there's a lot of machinery there, and it may be more heavy-weight than you need. For example, the WrapperTraits template could probably be eliminated in favor of simply using WrappedType and GetRootType directly. I can't imagine you'll often need to pass a WrapperTraits instantiation around.

Currying for templates in C++ metaprogramming

This is more of a conceptual question. I'm trying to find the easiest way of converting a two-arg template (the arguments being types) into a one-arg template. I.e., binding one of the types.
This would be the meta-programming equivalent of bind in boost/std. My example includes a possible use-case, which is, passing std::is_same as template argument to a template that takes a one-arg template template argument (std::is_same being a two-arg template), i.e. to TypeList::FindIf. The TypeList is not fully implemented here, neither is FindIf, but you get the idea. It takes a "unary predicate" and returns the type for which that predicate is true, or void if not such type.
I have 2 working variants but the first is not a one-liner and the 2nd uses a rather verbose BindFirst contraption, that would not work for non-type template arguments. Is there a simple way to write such a one-liner? I believe the procedure I'm looking for is called currying.
#include <iostream>
template<template<typename, typename> class Function, typename FirstArg>
struct BindFirst
{
template<typename SecondArg>
using Result = Function<FirstArg, SecondArg>;
};
//template<typename Type> using IsInt = BindFirst<_EqualTypes, int>::Result<Type>;
template<typename Type> using IsInt = std::is_same<int, Type>;
struct TypeList
{
template<template<typename> class Predicate>
struct FindIf
{
// this needs to be implemented, return void for now
typedef void Result;
};
};
int main()
{
static_assert(IsInt<int>::value, "");
static_assert(!IsInt<float>::value, "");
// variant #1: using the predefined parameterized type alias as predicate
typedef TypeList::FindIf<IsInt>::Result Result1;
// variant #2: one-liner, using BindFirst and std::is_same directly
typedef TypeList::FindIf< BindFirst<std::is_same, int>::Result>::Result Result2;
// variant #3: one-liner, using currying?
//typedef TypeList::FindIf<std::is_same<int, _>>::Result Result2;
return 0;
}
Click here for code in online compiler GodBolt.
I think the typical way of doing this is keep everything in the world of types. Don't take template templates - they're messy. Let's write a metafunction named ApplyAnInt that will take a "metafunction class" and apply int to it:
template <typename Func>
struct ApplyAnInt {
using type = typename Func::template apply<int>;
};
Where a simple metafunction class might be just checking if the given type is an int:
struct IsInt {
template <typename T>
using apply = std::is_same<T, int>;
};
static_assert(ApplyAnInt<IsInt>::type::value, "");
Now the goal is to support:
static_assert(ApplyAnInt<std::is_same<_, int>>::type::value, "");
We can do that. We're going to call types that contain _ "lambda expressions", and write a metafunction called lambda which will either forward a metafunction class that isn't a lambda expression, or produce a new metafunction if it is:
template <typename T, typename = void>
struct lambda {
using type = T;
};
template <typename T>
struct lambda<T, std::enable_if_t<is_lambda_expr<T>::value>>
{
struct type {
template <typename U>
using apply = typename apply_lambda<T, U>::type;
};
};
template <typename T>
using lambda_t = typename lambda<T>::type;
So we update our original metafunction:
template <typename Func>
struct ApplyAnInt
{
using type = typename lambda_t<Func>::template apply<int>;
};
Now that leaves two things: we need is_lambda_expr and apply_lambda. Those actually aren't so bad at all. For the former, we'll see if it's an instantiation of a class template in which one of the types is _:
template <typename T>
struct is_lambda_expr : std::false_type { };
template <template <typename...> class C, typename... Ts>
struct is_lambda_expr<C<Ts...>> : contains_type<_, Ts...> { };
And for apply_lambda, we just will substitute the _ with the given type:
template <typename T, typename U>
struct apply_lambda;
template <template <typename...> class C, typename... Ts, typename U>
struct apply_lambda<C<Ts...>, U> {
using type = typename C<std::conditional_t<std::is_same<Ts, _>::value, U, Ts>...>::type;
};
And that's all you need actually. I'll leave extending this out to support arg_<N> as an exercise to the reader.
Yeah, I had this issue to. It took a few iterations to figure out a decent way to do this. Basically, to do this, we need to specify a reasonable representation of what we want and need. I borrowed some aspects from std::bind() in that I want to specify the template that I wish to bind and the parameters that I want to bind to it. Then, within that type, there should be a template that will allow you to pass a set of types.
So our interface will look like this:
template <template <typename...> class OP, typename...Ts>
struct tbind;
Now our implementation will have those parameters plus a container of types that will be applied at the end:
template <template <typename...> class OP, typename PARAMS, typename...Ts>
struct tbind_impl;
Our base case will give us a template type, which I'll call ttype, that'll return a template of the contained types:
template <template <typename...> class OP, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>>
{
template<typename...Us>
using ttype = OP<Ss...>;
};
Then we have the case of moving the next type into the container and having ttype refer to the ttype in the slightly simpler base case:
template <template <typename...> class OP, typename T, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, T, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, std::tuple<Ss..., T>
, Ts...
>::template ttype<Us...>;
};
And finally, we need a remap of the templates that will be passed to ttype:
template <template <typename...> class OP, size_t I, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, std::integral_constant<size_t, I>, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, typename std::tuple<
Ss...
, typename std::tuple_element<
I
, typename std::tuple<Us...>
>::type
>
, Ts...
>::template ttype<Us...>;
Now, since programmers are lazy, and don't want to type std::integral_constant<size_t, N> for each parameter to remap, we specify some aliases:
using t0 = std::integral_constant<size_t, 0>;
using t1 = std::integral_constant<size_t, 1>;
using t2 = std::integral_constant<size_t, 2>;
...
Oh, almost forgot the implementation of our interface:
template <template <typename...> class OP, typename...Ts>
struct tbind : detail::tbind_impl<OP, std::tuple<>, Ts...>
{};
Note that tbind_impl was placed in a detail namespace.
And voila, tbind!
Unfortunately, there is a defect prior to c++17. If you pass tbind<parms>::ttype to a template that expects a template with a particular number of parameters, you will get an error as the number of parameters don't match (specific number doesn't match any number). This complicates things slightly requiring an additional level of indirection. :(
template <template <typename...> class OP, size_t N>
struct any_to_specific;
template <template <typename...> class OP>
struct any_to_specific<OP, 1>
{
template <typename T0>
using ttype = OP<T0>;
};
template <template <typename...> class OP>
struct any_to_specific<OP, 2>
{
template <typename T0, typename T1>
using ttype = OP<T0, T1>;
};
...
Using that to wrap tbind will force the compiler to recognize the template having the specified number of parameters.
Example usage:
static_assert(!tbind<std::is_same, float, t0>::ttype<int>::value, "failed");
static_assert( tbind<std::is_same, int , t0>::ttype<int>::value, "failed");
static_assert(!any_to_specific<
tbind<std::is_same, float, t0>::ttype
, 1
>::ttype<int>::value, "failed");
static_assert( any_to_specific<
tbind<std::is_same, int , t0>::ttype
, 1
>::ttype<int>::value, "failed");
All of which succeed.