9

I have this code:

struct Base {};

template<typename T>
struct Foo : Base {};

struct Bar {
    template<typename T> //           v--- What's happening here?
    Bar(T foo) : baz{std::make_unique<Foo>(foo)} {}

    std::unique_ptr<Base> baz;
};

As a surprise, GCC and Clang accept and compiles it. It seem to deduce the template parameter of Foo, but it wouldn't make sense. How come the compilers accept that even if there is no overload of std::make_unique that takes a template template parameter? Live example

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • 3
    Err... try creating a `Bar` object with that constructor, if at all you can. Then tell us if the compiler accepts the code. – WhiZTiM Mar 09 '17 at 22:43
  • @WhiZTiM it refuse the code. I still find it really odd that both compiler don't complain about mismatch template parameter type. – Guillaume Racicot Mar 10 '17 at 00:34

2 Answers2

8

There are some situations where a template is always invalid, no matter what template arguments are supplied, but the compiler isn't able to figure that out, because it doesn't have the ability to try substituting every possible set of template arguments. According to the standard ([temp.res]/8):

If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.

That means that the compiler is allowed to be smart and prove that there is no valid specialization, and produce a compilation error, but it's also allowed to not be smart enough, and not produce a compilation error. Of course, once the template is instantiated, then the compiler must diagnose the error.

It's not illegal to use the name of a template without template arguments. There are some circumstances where the compiler can deduce arguments. For example:

template <class T>
void foo(T x);

int main() {
    void (*p)(int) = foo;  // p points to foo<int>
}

In your code, it turns out that you've used Foo in a context where the template arguments can't be deduced. If the compilers were smarter, they would have figured that out. But the fact that they didn't manage to figure it out does not mean your code is correct.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 1
    Nice code snippet, had no idea that such a code is valid and the template parameter can be deduced in such a context. – vsoftco Mar 09 '17 at 22:57
4

The reason this works is because C++ doesn't actually create template functions until they're called from somewhere in the code. In this instance, since your code doesn't ever try to create a Bar, your template one-arg Bar constructor is never generated, so the compiler never needs to check if the code is correct.

Template functions are generated by the compiler as soon as they're used, and they're generated only based on the types passed into them. So, if you try to create a Bar like so:

Foo<int> f;
Bar b = Bar(f);

The compiler will try to generate the constructor and fail with an error like:

error C2955: 'Foo' : use of class template requires template argument list

Because it now knows the code is wrong.

If you think about it, this is how it has to work: since C++ doesn't allow you to put constraints on template types, the compiler would have to try every possible combination of template types to figure out if a template function has syntax errors.

Daniel Eisener
  • 323
  • 1
  • 4
  • The compiler performs some template checks as well, but in this case indeed there's no check performed. The rules are actually pretty complicated, see [C++ Templates: A complete guide](https://www.amazon.com/Templates-Complete-Guide-David-Vandevoorde/dp/0201734842) for a detailed explanation. – vsoftco Mar 10 '17 at 00:00
  • Yes you can constrain any template parameter by emulating concept through sfinae. – Guillaume Racicot Mar 10 '17 at 03:47
  • 1
    @GuillaumeRacicot That doesn't allow you to constrain the types that can be used to instantiate a template function. It only allows you to create syntax errors in certain overloads, so that that instantiation will be given lower priority in overload resolution. Any type can still be used for any template type, so the compiler still needs to consider any type as a possible candidate. This makes it impossible to check for syntax errors before a specific instantiation is chosen, as I said. – Daniel Eisener Mar 10 '17 at 16:55
  • @DanielEisener You are right indeed, even with sfinae what you do with a dependent type doesn't matter until instantiation. – Guillaume Racicot Mar 10 '17 at 20:15