Variadic template aliases as template arguments (part 2)
Asked Answered
P

2

13

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.

Popple answered 29/11, 2013 at 18:8 Comment(3)
Whatever your clang version is, clang 3.5 still says 0, 0. (Concerning the first test).Cristalcristate
Any news on this? Have exactly the same problem/question.Cubit
This is an open issue with the standard: open-std.org/JTC1/SC22/WG21/docs/cwg_active.html#1430Cubit
P
2

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.

Popple answered 13/3, 2014 at 9:35 Comment(0)
O
0

I think the situation is pretty well standardized; C++11 14.3.3/1 says:

A template-argument for a template template-parameter shall be the name of a class template or an alias template, expressed as id-expression.

Odontalgia answered 29/11, 2013 at 18:19 Comment(1)
Thanks. I've seen the same quote in the answer to the previous question, but as I'm not familiar at all with he language of the standard, it looks quite vague to me. So basically what you say is that the problem is with the implementations and I should apply any workaround until problems are fixed, at which point this code will work. Right? In this case, I might also report a bug?Popple

© 2022 - 2024 — McMap. All rights reserved.