14

I am writing some TMP-heavy code for g++ (version 4.8.1_1, Macports) and clang++ (version 3.3, Macports). While g++ rejects the following code listing with UNBRIDLED FURY, clang++ compiles it with grace and splendor.

  • Which complier is in the right? (I strongly suspect it's g++, but I want to get some reassurance from others before submitting a bug report.)
  • Do you have any easy or elegant workarounds to suggest? (I need to use template aliases, so switching over to structs, which causes g++ to accept the code, is not an option.)

Here is the code listing, made just for you.

template <class... Ts>
struct sequence;

template <int T>
struct integer;

// This definition of `extents` causes g++ to issue a compile-time error.
template <int... Ts>
using extents = sequence<integer<Ts>...>;

// However, this definition works without any problems.
// template <int... Ts>
// struct extents;

template <int A, int B, class Current>
struct foo;

template <int A, int B, int... Ts>
struct foo<A, B, extents<Ts...>>
{
    using type = int;
};

template <int B, int... Ts>
struct foo<B, B, extents<Ts...>>
{
    using type = int;
};

int main()
{
    using t = foo<1, 1, extents<>>::type;
    return 0;
}

Here is g++'s output:

er.cpp: In function 'int main()':
er.cpp:39:41: error: ambiguous class template instantiation for 'struct foo<1, 1, sequence<> >'
  using t = typename foo<1, 1, extents<>>::type;
                                         ^
er.cpp:26:8: error: candidates are: struct foo<A, B, sequence<integer<Ts>...> >
 struct foo<A, B, extents<Ts...>>
        ^
er.cpp:32:8: error:                 struct foo<B, B, sequence<integer<Ts>...> >
 struct foo<B, B, extents<Ts...>>
        ^
er.cpp:39:43: error: 'type' in 'struct foo<1, 1, sequence<> >' does not name a type
  using t = typename foo<1, 1, extents<>>::type;
                                           ^

Here is clang++'s output:

Thanks for your help!

void-pointer
  • 14,247
  • 11
  • 43
  • 61

3 Answers3

8

This seems like a g++ bug because clearly foo<B, B, extents> is more specialized than foo<A, B, extents> (te latter can match anything that the former matches, but not vice versa), so the compiler should choose that specialization.

As you noted yourself, changing extents from a template alias to a class template, solves the problem.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
3

The question boils down if I understood it correctly to determining whether one of the following template specializations is more specialized than the other:

template <int A, int B, class Current>
struct foo;

template <int A, int B, int... Ts>
struct foo<A, B, extents<Ts...>>
{
    using type = int;
};

template <int B, int... Ts>
struct foo<B, B, extents<Ts...>>
{
    using type = int;
};

And the answer is yes, for any combination of parameters that are allowed in the second specialization the same combination is allowed in the first by making the template arguments A == B. On the other direction, any instantiation of the first template specialization on which A != B cannot be a match for the second specialization, so the second is strictly more specialized than the first.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
0

I believe g++ might be correct. The line

using t = foo<1, 1, extents<>>::type

is using the template parameters in a non-deduced context, so it can't use the actual values given for the template parameters to resolve the ambiguity, only their types and that isn't sufficient.

Section 14.8.2.5, para 4 of the C++ standard says:

In most cases, the types, templates, and non-type values that are used to compose P participate in template argument deduction. That is, they may be used to determine the value of a template argument, and the value so determined must be consistent with the values determined elsewhere. In certain contexts, however, the value does not participate in type deduction, but instead uses the values of template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

The non-deduced contexts are:

x The nested-name-specifier of a type that was specified using a qualified-id

...

Section 14.8.2.4 para 11 says:

In most cases, all template parameters must have values in order for deduction to succeed, but for partial ordering purposes a template parameter may remain without a value provided it is not used in the types being used for partial ordering. [ Note: A template parameter used in a non-deduced context is considered used. —end note ]

So we're in a non-deduced context, which means that all template arguments must have values. So if extents<> fails to nail down the type, then the result will be ambiguous according to the standard. Plausible?

Phil Armstrong
  • 1,796
  • 1
  • 10
  • 17
  • The "P" expressions are `A`, `B`, `extents`. The "A" expressions are `1`, `1`, `extents<>`. No "P" expression uses any template parameter in a non-deduced context. – aschepler Jul 17 '13 at 20:27
  • Doesn't the ::type on the end make it a qualified-id? – Phil Armstrong Jul 17 '13 at 20:44
  • Yes, `foo<1, 1, extents<>>::type` is a qualified-id. But it does not use any template parameters. It does contain some template arguments ("A" in 14.8.2). The arguments in the qualified-id are compared to for deduction; they are not being deduced. – aschepler Jul 17 '13 at 20:51
  • A little later, the standard says "When a type name is specified in a way that includes a non-deduced context, all of the types that comprise that type name are also non-deduced. However, a compound type can include both deduced and non-deduced types. [Example ... If a type is specified as void f(typename A::B, A), the T in A::B is non-deduced but the T in A is deduced.—end example]" T is not a qualified type in A::B yet the standard says that that element is evaluated in a non-deduced context. Does the same not apply here? – Phil Armstrong Jul 17 '13 at 21:37
  • Also, if this logic were valid, type deduction would fail for `std::vector::iterator iter;` – aschepler Jul 17 '13 at 21:40
  • Hmm. vector doesn't need to resolve between different template specialisations though does it? – Phil Armstrong Jul 17 '13 at 21:44
  • That depends on the implementation. But probably not. – aschepler Jul 17 '13 at 21:48