Violet Giraffe
Violet Giraffe

Reputation: 33579

Template template argument deduction failure with GCC (works with MSVC)

I have the following reasonably simple function template:

template <class OrderedSetType, template<class> class SupersetType>
OrderedSetType f(const SupersetType<OrderedSetType>& superset)
{
    return OrderedSetType();
}

It's called like this:

f(std::vector<std::string>());

And the compiler fails to deduce the template parameter. The diagnostic message isn't particularly helpful:

<source>: In function 'int main()':

<source>:12:33: error: no matching function for call to 'f(std::vector<std::__cxx11::basic_string<char> >)'

     f(std::vector<std::string>());

                                 ^

<source>:5:16: note: candidate: template<class OrderedSetType, template<class> class SupersetType> OrderedSetType f(const SupersetType<OrderedSetType>&)

 OrderedSetType f(const SupersetType<OrderedSetType>& superset)

                ^

<source>:5:16: note:   template argument deduction/substitution failed:

<source>:12:33: note:   template parameters of a template template argument are inconsistent with other deduced template arguments

     f(std::vector<std::string>());

                                 ^

Why does the error occur? Happens with GCC 7.3 with -std=c++14, does not happen with -std=c++17. Which changes in the C++ 17 standard allowed for this code to compile? And can I make it compile for C++14?

Here's the live demo: https://godbolt.org/g/89BTzz

Specifying the template arguments explicitly doesn't help, by the way.

P. S. In the meantime, MSVC has no problems with this piece of code, but clang 5 and 6 cannot compile it even in C++17 mode. So either clang has a bug and fails to compile standard-compliant code, or GCC has a bug and successfully compiles code that it shouldn't (with -std=c++17).

Upvotes: 1

Views: 999

Answers (3)

songyuanyao
songyuanyao

Reputation: 172924

Which changes in the C++ 17 standard allowed for this code to compile?

You're declaring the template template parameter SupersetType contaning only one template parameter, but the template template argument std::vector<std::string> has two, i.e. std::string and the default template argument std::allocator<string>. Before C++17 they don't match and leads to error (then you have to make them match to solve the issue), since C++17 (CWG 150) it's allowed; i.e. the default template arguments are allowed for a template template argument to match a template template parameter with fewer template parameters.

template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template <class ...Types> class C { /* ... */ };

template<template<class> class P> class X { /* ... */ };
X<A> xa; // OK
X<B> xb; // OK in C++17 after CWG 150
         // Error earlier: not an exact match
X<C> xc; // OK in C++17 after CWG 150
         // Error earlier: not an exact match

Upvotes: 3

max66
max66

Reputation: 66200

Try with

template <template <typename...> class SupersetType,
          typename FirstT, typename ... OthersTs>
FirstT f (SupersetType<FirstT, OthersTs...> const & superset)
 { return FirstT{}; }

or also

template <template <typename...> class SupersetType, typename FirstT>
FirstT f (SupersetType<FirstT> const & superset)
 { return FirstT{}; }

The problem is that std::vector doesn't accept only a type but two; the second is an allocator with a default value.

So you have to take in count this problem.

Obviously you can write f() with a template-template parameter that accept only two types

template <template <typename, typename> class SupersetType,
          typename FirstT, typename SecondT>
FirstT f (SupersetType<FirstT, SecondT> const & superset)
 { return FirstT{}; }

but if you use a template parameter that accept a variadic list of types, you have a more flexible f() (that match more containers)

Upvotes: 2

Some programmer dude
Some programmer dude

Reputation: 409176

While this doesn't provide an answer to your problem, it provide an alternative.

Remember that all standard container have a public type named value_type. That means you could easily skip the template template and only have something like

template<typename ContainerT>
typename ContainerT::value_type f(ContainerT const& superset)
{
    return typename ContainerT::value_type();
}

As long as your SupersetType follows the standard containers with a value_type member, it should work.

Upvotes: 1

Related Questions