Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic template aliases as template arguments (part 2)

This is a follow-up of another question. It refers to the same problem (I hope) but uses an entirely different example to illustrate it. The reason is that in the previous example only experimental GCC 4.9 failed with a compiler error. In this example, also Clang and GCC 4.8.1 fail in different ways: Clang produces an unexpected result and GCC 4.8.1 reports a different error message.

Answers to the previous question say more or less that the code is valid and the problem lies with the experimental version of GCC. But this result makes me a bit more sceptical. I have been troubled for months with problems that I suspect are related (or the same), and this is the first time I have a small concrete example to illustrate.

So, here is some code. First, some generic code that applies SFINAE to an arbitrary test as specified by a variadic template alias metafunction F:

#include <iostream>
using namespace std;

using _true  = integral_constant <bool, true>;
using _false = integral_constant <bool, false>;

template <typename T> using pass = _true;

template <template <typename...> class F>
struct test
{
    template <typename... A> static _false           _(...);
    template <typename... A> static pass <F <A...> > _(int);
};

template <template <typename...> class F, typename... A>
using sfinae = decltype(test <F>::template _<A...>(0));

Second, a specific test, checking if a given class has defined a type named type:

template <typename T> using type_of  = typename T::type;
template <typename T> using has_type = sfinae <type_of, T>;

Finally, an example:

struct A { using type = double; };

int main()
{
    cout << has_type <int>() << ", ";
    cout << has_type <A>()   << endl;
}

The expected result would be 0, 1. Clang says 0, 0. GCC 4.8.1 says

tst.cpp: In substitution of ‘template<class T> using type_of = typename T::type [with T = A ...]’:
tst.cpp:15:51: required from ‘struct test<type_of>’
tst.cpp:19:67: required by substitution of ‘template<template<class ...> class F, class ... A> using sfinae = decltype (test:: _<A ...>(0)) [with F = type_of; A = {T}]’
tst.cpp:24:58: required from here
tst.cpp:23:56: error: ‘A ...’ is not a class, struct, or union type
  template <typename T> using type_of = typename T::type; 
                                                        ^

and GCC 4.9 says

tst.cpp:19:67:   required by substitution of ‘template<template<class ...> class F, class ... A> using sfinae = decltype (test:: _<A ...>(0)) [with F = type_of; A = {T}]’
tst.cpp:24:58:   required from here
tst.cpp:15:51: error: pack expansion argument for non-pack parameter ‘T’ of alias template ‘template<class T> using type_of = typename T::type’
  template <typename... A> static pass <F <A...> > _(int);
                                                   ^

(line numbers may vary). So, everything fails, in different ways.

Now, here is a workaround. Metafunction car picks the first type fom a given pack, and then the test is redefined as type_of2, now being variadic:

template <typename... T> struct car_t;
template <typename... T> using  car = type_of <car_t <T...> >;

template <typename T, typename... Tn>
struct car_t <T, Tn...> { using type = T; };

template <typename... T> using type_of2  = typename car <T...>::type;
template <typename T>    using has_type2 = sfinae <type_of2, T>;

int main()
{
    cout << has_type2 <int>() << ", ";
    cout << has_type2 <A>()   << endl;
}

Now all three compilers say 0, 1 as expected. It is interesting that for any version of GCC we have to remove has_type (even if we don't use it) and leave only has_type2; otherwise we have similar error.

To wrap up: I see the problem with one template expecting a variadic template-parameter of the form

template <typename...> class F

where we actually give as input a non-variadic template alias of the form

template <typename T> using alias = // ... anything including T or not

and finally invoke F as if it was variadic:

F <A...>

Opinions so far say this is valid, but now it seems three compilers disagree. So the question is again: is it valid?

To me it matters because I have dozens of files of existing code based on the assumption that this is valid, and now I need a redesign anyway (since there are practical problems with these compilers) but the exact redesign will depend on the answer.

like image 799
iavr Avatar asked Nov 29 '13 18:11

iavr


People also ask

What is Variadic template in 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.

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


1 Answers

This does not answer the question whether the code above is valid, but is a quite pretty workaround that I have found by experimenting shortly after asking the question, and I think is useful to share.

All that is needed are the following definitions:

template <template <typename...> class F>
struct temp { };

template <typename... A, template <typename...> class F>
F <A...> subs_fun(temp <F>);

template <template <typename...> class F, typename... A>
using subs = decltype(subs_fun <A...>(temp <F>()));

then, wherever F <A...> would be problematic, replace it with subs <F, A...>. That's it. I cannot explain why, but it has worked in all cases so far.

For instance, in the SFINAE example of the question, just replace line

template <typename... A> static pass <F <A...> > _(int);

by

template <typename... A> static pass <subs <F, A...> > _(int);

This is a change at one point only, all remaining code stays the same. You don't need to redefine or wrap every template metafunction that with be used as F. Here's a live example.

If F <A...> is indeed valid and compilers support it eventually, it is again easy to switch back because changes are minimal.

I find this important because it allows specifying a SFINAE test in just two lines

template <typename T> using type_of  = typename T::type;
template <typename T> using has_type = sfinae <type_of, T>;

and is completely generic. Typically, each such test needs at least 10 lines of code and implementations of <type_traits> are full of such code. In some cases such code blocks are defined as macros. With this solution, templates can do the job and macros are not needed.

like image 195
iavr Avatar answered Oct 16 '22 00:10

iavr