c++templatesc++14type-traitstemplate-aliases

Template aliases conflicting types. g++ compiles successfully while clang fails


I encountered a very strange compiler error. For some reason the posted code does compile properly with g++ (7.3.0) while clang (7.0.0) fails:

../TemplateAlias/main.cpp:64:9: error: no matching function for call to 'freeFunc'
        freeFunc(new Func, dummyField);
        ^~~~~~~~
../TemplateAlias/main.cpp:73:12: note: in instantiation of member function 'Helper<Traits<double, ConcreteData, ConcreteField> >::func' requested here
    helper.func();
           ^
../TemplateAlias/main.cpp:21:13: note: candidate template ignored: deduced conflicting templates for parameter '' ('FieldData' vs. 'ConcreteData')
static void freeFunc(SomeFunc<T, FieldData>* func,
            ^

Both compiler options were set to -std=c++14

template<typename T>
struct ConcreteData
{
    T data;
};

template<typename T, template<typename U> class FieldData>
struct ConcreteField
{
    FieldData<T> someMember;
};

template<typename T, template<typename U> class FieldData>
struct SomeFunc
{

};


template<typename T, template<typename U> class FieldData>
static void freeFunc(SomeFunc<T, FieldData>* func,
                     ConcreteField<T, FieldData>& field)
{
    // apply the func on data
    (void)field; // silence compiler warning
    delete func;
}


template<
        typename ScalarType,
        template<typename U> class FieldDataType,
        template<typename U, template <typename X> class Data> class FieldType
        >
struct Traits
{
    using Scalar = ScalarType;

    template<typename T>
    using FieldData = FieldDataType<T>;

    using Field = FieldType<Scalar, FieldDataType>; // fails with clang only
    // using Field = FieldType<Scalar, FieldData>; // using this line helps clang

};

template<typename Traits>
struct Helper
{
    // alias all types given by trait for easier access
    using Scalar = typename Traits::Scalar;
    using Field = typename Traits::Field;

    template<typename U>
    using DataAlias = typename Traits::template FieldData<U>;

    void func()
    {
         // using Func = SomeFunc<Scalar, DataAlias>; // this line is intended, but fails with both GCC and clang
         using Func = SomeFunc<Scalar, Traits::template FieldData>; // compiles only with GCC, fails with clang


        Field dummyField;
        freeFunc(new Func, dummyField);
    }
};


int main()
{
    using ConcreteTraits = Traits<double, ConcreteData, ConcreteField>;
    Helper<ConcreteTraits> helper;
    helper.func();

    return 0;
}

According to cppreference.com:

A type alias declaration introduces a name which can be used as a synonym for the type denoted by type-id. It does not introduce a new type and it cannot change the meaning of an existing type name. There is no difference between a type alias declaration and typedef declaration. This declaration may appear in block scope, class scope, or namespace scope.

and

Alias templates are never deduced by template argument deduction when deducing a template template parameter.

In my understanding both types (ConcreteData and FieldData) should be equivalent. Why is clang failing in this condition and why do both compiler fail when using the "second stage" alias? Which compiler is right according to the C++ standard? Is it a compiler bug or a subtle ambiguous interpretation of the C++14 standard?


Solution

  • Borrowing the minimal example of @Oktalist.

    template <typename>
    class T {};
    
    template <typename _>
    using U = T<_>;
    
    template <template <typename> class X>
    void f(A<X>, A<X>) {}
    

    if you replace f by:

    template <template <typename> class X, template <typename> class Y>
    void f(A<X>, A<Y>) {}
    

    the code no longer fail to compile. You can see that the problem is about equivalence of template parameters X and Y, they are deduced to different types.

    The equivalence of types produced by alias template are only considered when referring to specialization of the alias, as is specified on [temp.alias]/2:

    When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template

    Using this rule and the rules for equivalence [temp.type]/1:

    T<int> and U<int> are equivalent, so are X<T<int>> and Z<U<int>>, but this rule doesn't extend to the alias template U being equivalent to the class template T (by themselves, they aren't specializations).

    This is the same scenario for the alias FieldData and the class template ConcreteData.

    There are in fact two defect report, CWG-1286 and CWG-1244 that propose the equivalence extension for alias templates.