havogt
havogt

Reputation: 2812

Use SFINAE to check if types can be bound to template template parameter

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

Answers (2)

Oktalist
Oktalist

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

DEMO

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.

DEMO

Upvotes: 2

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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> >;

Live example.

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

Related Questions