4
votes

I want a helper function to instantiate a class for me. Currently it cannot compile in clang (though it compiles work in gcc), but I need it to work in clang as well. Currently I'm using clang version 6.0.0-1ubuntu2.

I'm not sure why it's failing, since gcc is able to detect the type. I tried doing stuff from this post and playing around with it for a while but I keep running into a wall. MCVE available, or you can try it on coliru here:

#include <vector>

using namespace std;

template <typename T, template <typename> typename Container>
struct SomeClass {
    SomeClass(const Container<T>& c) {
    }
};

template <typename T, template <typename> typename C>
inline auto make_some_class(const C<T>& container) {
    return SomeClass<T, C>(container);
}

int main() {
    vector<int> ints;

    auto stuff = make_some_class(ints);  
}

main.cpp:19:18: error: no matching function for call to 'make_some_class'

   auto stuff = make_some_class(ints);  

                ^~~~~~~~~~~~~~~

main.cpp:12:13: note: candidate template ignored: substitution failure [with T = int]: template template argument has different template parameters than its corresponding template template parameter

inline auto make_some_class(const C<T>& container) {

            ^

1 error generated.

3
This looks like non-deducible context, even if we temporarily ignore the basic fact that std::vector has two template parameters, not one, and will not match template<typename> typename Container in the first place.Sam Varshavchik
I expect you are using Clang and it would work if you add a compilation flag -frelaxed-template-template-args.Hiroki
@SamVarshavchik It is not a non-deducible context.Barry

3 Answers

5
votes

Suggestion: try with

#include <vector>

template <template <typename...> typename Container, typename ... Ts>
struct SomeClass {
    SomeClass(const Container<Ts...>& c) {
    }
};

template <template <typename...> typename C, typename ... Ts>
inline auto make_some_class(const C<Ts...>& container) {
    return SomeClass<C, Ts...>(container);
}

int main() {
    std::vector<int> ints;

    auto stuff = make_some_class(ints);  
}

I mean... I suppose the problem is that std::vector isn't a container that receive one type template parameter; it's a container that receive two type template parameter (the second one with a default type: std::allocator<T> where T is the first one).

So the suggestion is: make SomeClass more flexible and able to receive a container with an variadic list of template types of arguments

template <typename...> typename Container

and the corresponding list of template types

typename ... Ts

If you want a variadic list of arguments (Ts...) you need it in last position so you have to switch the position of Container and T (now Ts...): before Container and after the variadic list Ts...

template <template <typename...> typename Container, typename ... Ts>
struct SomeClass {
    SomeClass(const Container<Ts...>& c) {
    }
};

Not strictly required but, for uniformity, I suggest to rewrite make_some_class() in the same way (and, obviously, pass C before Ts... in the template parameters list).

template <template <typename...> typename C, typename ... Ts>
inline auto make_some_class(const C<Ts...>& container) {
    return SomeClass<C, Ts...>(container);
}
5
votes

Let's reduce this to just:

template <template <typename> class C, typename T>
void foo(C<T> const&) { }

std::vector<int> v;
foo(v);

You'll find that gcc compiles that but clang does not. The reasons for both are interesting.

First, recall that std::vector is a class template that takes two template parameters:

template <typename T, typename Alloc = std::allocator<T>>
class vector { ... };

Why does gcc think that matches template <typename> class C - a template which has only one template parameter? Because the rules changed as a result of P0522R0. And it makes sense - we can use vector as if it had one type parameter in normal code, so it should be able to match such a template parameter.

Now, why does clang think that vector doesn't match? Because they explicitly are choosing to not adopt this rule. From their docs:

(10): Despite being the resolution to a Defect Report, this feature is disabled by default in all language versions, and can be enabled explicitly with the flag -frelaxed-template-template-args in Clang 4 onwards. The change to the standard lacks a corresponding change for template partial ordering, resulting in ambiguity errors for reasonable and previously-valid code. This issue is expected to be rectified soon.

That is, it might break code.


Of course you might just want to know how to fix it. Just change the declaration of the template template parameter C:

template <template <typename...> class C, typename T>
void foo(C<T> const&) { }

std::vector<int> v;
foo(v); // ok in both gcc and clang
2
votes

As already suggested in comment and max66, std::vector has two template parameters, the value_type and allocator_type. Before C++17, we must explicitly write both parameters as template template parameters in this case as follows and this would also work in C++17:

DEMO

template <typename T, template <typename V, typename Allocator = std::allocator<T>> typename Container>
struct SomeClass {
    SomeClass(const Container<T>& c) {
    }
};

template <typename T, template <typename V, typename Allocator = std::allocator<T>> typename C>
inline auto make_some_class(const C<T>& container) {
    return SomeClass<T, C>(container);
}

BTW, since C++17, "Matching template template parameters to compatible arguments" [P0522R0] is accepted and your code is right if you are using C++17. But Clang still disable this new feature at default and the compilation flag -frelaxed-template-template-args enables this feature.

DEMO