Reputation: 2812
Is it possible to test if some types can bind to a template template parameter via SFINAE?
I think what I try to do is best explained with the following example code:
#include <iostream>
template<typename... T> using void_t = void;
template<typename T> struct TemporaryBindObject
{
using type = TemporaryBindObject<T>;
};
template<template<typename...> class Dest> struct TestValidBind
{
template<typename... Ts> struct toTypesOf
{
using type = std::false_type;
};
template<template<typename...> class Src, typename... Ts> struct toTypesOf<Src<Ts...>, void_t<Dest<Ts...,float>>>
{
using type = std::true_type;
};
};
template<typename T> struct OneParamStruct{};
template<typename T1, typename T2> struct TwoParamStruct{};
int main()
{
using tmp = TemporaryBindObject<int>;
std::cout << "Can bind to TwoParamStruct: " << TestValidBind<TwoParamStruct>::toTypesOf<tmp>::type::value << std::endl;
std::cout << "Can bind to OneParamStruct: " << TestValidBind<OneParamStruct>::toTypesOf<tmp>::type::value << std::endl;
}
First I make a temporary type tmp
from which I want to take the template parameter int
to bind it to another class template.
With the TestValidBind<template template type>::toTypesOf<typename>
I want to test if it is possible to bind the parameter of the given type to the template template parameter
and and append an additional type (float
in the example).
What I want is that TestValidBind<TwoParamStruct>::toTypesOf<tmp>::type
is true_type
while TestValidBind<OneParamStruct>::toTypesOf<tmp>::type
is false_type
.
The code example as it is does not compile with g++ -std=c++11
(5.3.1) with the following error:
../test_SFINAE_with_template_binding.cc: In function ‘int main()’: ../test_SFINAE_with_template_binding.cc:34:96: error: ‘TestValidBind<OneParamStruct>::toTypesOf<TemporaryBindObject<int> >::type’ has not been declared
and reports false_type
(which is wrong) if the OneParamStruct
line is removed.
With clang++ -std=c++11
(3.8.0) the code compiles but reports false_type
in both cases.
Is something like that possible at all?
Edit: Changed the additional type from void
to float
to highlight that I want to check if the additional type is possible.
Upvotes: 2
Views: 222
Reputation: 14714
The void_t
trick requires that we give one of the parameters of the primary template a default type of void
. You had no need for the primary template (toTypesOf
) to be variadic.
It is more idiomatic for a bool
type trait to inherit from false_type
or true_type
instead of having a nested type
. There is no need for TemporaryBindObject
to have a nested type
.
TestValidBind
should look something like this:
template<template<typename...> class Dest> struct TestValidBind
{
template<template<typename...> class Dest, typename T, typename = void_t<>> struct toTypesOf
: std::false_type
{};
template<template<typename...> class Dest, template<typename...> class Src, typename... Ts> struct toTypesOf<Dest, Src<Ts...>, void_t<Dest<Ts..., float>>>
: std::true_type
{};
};
EDIT: As you say, this doesn't work with g++, and I don't know why that is. But we can simplify it and see if that helps. We don't actually need the enclosing struct TestValidBind
. The toTypesOf
template can be at namespace scope if we transplant the Dest
parameter into it:
template<template<typename...> class Dest, typename T, typename = void_t<>> struct toTypesOf
: std::false_type
{};
template<template<typename...> class Dest, template<typename...> class Src, typename... Ts> struct toTypesOf<Dest, Src<Ts...>, void_t<Dest<Ts..., float>>>
: std::true_type
{};
This works in g++.
We can go further and make it a bit easier to use if you like, by putting everything in a detail
namespace and wrapping it in an alias template:
namespace detail
{
template<typename... T> using void_t = void;
template<typename... T> struct TemporaryBindObject {};
template<template<typename...> class Dest, typename T, typename = void_t<>> struct toTypesOf
: std::false_type {};
template<template<typename...> class Dest, template<typename...> class Src, typename... Ts> struct toTypesOf<Dest, Src<Ts...>, void_t<Dest<Ts...>>>
: std::true_type {};
}
template<template<typename...> class Dest, typename... Ts>
using IsValidBind = typename detail::toTypesOf<Dest, detail::TemporaryBindObject<Ts...>>;
template<template<typename...> class Dest, typename... Ts>
using IsValidBindWithFloat = IsValidBind<Dest, Ts..., float>;
template<template<typename...> class Dest, typename... Ts>
using IsValidBindWithVoid = IsValidBind<Dest, Ts..., void>;
std::cout << "Can bind to TwoParamStruct: " << IsValidBindWithFloat<TwoParamStruct, int>::value << std::endl;
std::cout << "Can bind to OneParamStruct: " << IsValidBindWithFloat<OneParamStruct, int>::value << std::endl;
Now we don't need the using tmp
, and we have a more general solution in which you can easily change the type you want to use as the additional type.
Upvotes: 2
Reputation: 275730
Here is how I'd do it:
using std::void_t; // or write your own
template<class T>struct tag{using type=T;};
template<template<class...>class Z>struct ztag{
template<class...Ts>using result=Z<Ts...>;
};
namespace details {
template<class Src, class Target, class=void>
struct rebind {};
template<template<class...>class Src, template<class...>class Target, class...Ts>
struct rebind<Src<Ts...>, ztag<Target>, void_t<Target<Ts...>>>:
tag<Target<Ts...>>
{};
}
template<class Src, class zDest>
using rebind = typename details::rebind<Src,zDest>::type;
namespace details {
template<template<class...>class Z, class, class...Ts>
struct can_apply : std::false_type {};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...> : std::true_type {};
}
template<template<class...>class Z, class...Ts>
using can_apply = typename details::can_apply<Z, void, Ts...>::type;
template<class...>struct types{using type=types;};
namespace details {
template<class types, class...Us>
struct append;
template<class...Ts, class...Us>
struct append<types<Ts...>, Us...>:
types<Ts..., Us...>
{};
}
template<class types, class...Us>
using append = typename details::append<types, Us...>::type;
template<class Src, template<class...>class Dest>
using can_rebind_with_void =
can_apply< rebind, append< rebind<Src, ztag<types>>, void >, ztag<Dest> >;
The ztag
is because metaprogramming is much easier when working only with types. ztag
takes a template and turns it into a type.
zapply
then applies it:
namespace details {
template<class Z, class...Ts>
struct zapply {};
template<template<class...>class Z, class...Ts>
struct zapply<ztag<Z>, Ts...>:
tag<Z<Ts...>>
{};
}
template<class Z, class...Ts>
using zapply = typename details::zapply<Z,Ts...>::type;
In any case, everything is generic except the one-line solution:
template<class Src, template<class...>class Dest>
using can_rebind_with_void =
can_apply< rebind, append< rebind<Src, ztag<types>>, void >, ztag<Dest> >;
Here we ask "can we apply" rebind< ???, ztag<Dest> >
. This does a Dest<???>
.
???
starts with Src<???>
, moves its types over to types<???>
, appends a void
. So we get types<Ts..., void>
from Src<Ts...>
.
rebind
takes a type with template args, and a ztag
of another template, and applies the first arguments template args to the template inside the ztag
.
can_apply
asks if the implied template application is legal. If I was more consistent, can_apply
would also take a ztag
as its first argument.
The reason I do all this with ztag
is because template metaprogramming is smoother is everything is types.
Upvotes: 1