g++ Bug with Partial Template Specialization
Asked Answered
V

3

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!

Venue answered 17/7, 2013 at 12:32 Comment(6)
+1 for jokes alone :) I'd also bet that Clang is right here.Boadicea
The typename in main is unneededFrenchify
@DavidRodríguez-dribeas Thanks, it's become a habit...Venue
I need to use template aliases Mankind survived without them for the last several million years... I'm sure another five won't kill you.Doublecross
@LightnessRacesinOrbit This example was whittled down from thousands of lines of TMP-heavy code. This code uses template aliases heavily.Venue
To those who are interested, I have now submitted a bug report here.Venue
L
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.

Langland answered 17/7, 2013 at 12:49 Comment(0)
A
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.

Autotruck answered 17/7, 2013 at 12:59 Comment(0)
A
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?

Abroad answered 17/7, 2013 at 19:32 Comment(7)
The "P" expressions are A, B, extents<Ts...>. The "A" expressions are 1, 1, extents<>. No "P" expression uses any template parameter in a non-deduced context.Sanitary
Doesn't the ::type on the end make it a qualified-id?Abroad
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.Sanitary
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<T>::B, A<T>), the T in A<T>::B is non-deduced but the T in A<T> is deduced.—end example]" T is not a qualified type in A<T>::B yet the standard says that that element is evaluated in a non-deduced context. Does the same not apply here?Abroad
Also, if this logic were valid, type deduction would fail for std::vector<int>::iterator iter;Sanitary
Hmm. vector<int> doesn't need to resolve between different template specialisations though does it?Abroad
That depends on the implementation. But probably not.Sanitary

© 2022 - 2024 — McMap. All rights reserved.