3

gcc 11.2 can't seem to compile this:

template <typename T = int>
struct Test {};
template <typename T> void foo(T& bar) {}
int main()
{
    Test t;
    foo<Test>(t);
}

but has no problem with

template <typename T = int>
struct Test {};
template <typename T> void foo(T& bar) {}
int main()
{
    Test t;
    foo<Test<>>(t);
}

Is this a compiler bug?

This question seems to suggest it should work.

Radrich
  • 61
  • 4
  • FYI, the actual use-case that led to this is a bit more complex. I'll add some detail of that when I have time soon. – Radrich Apr 02 '22 at 12:27

3 Answers3

7

GCC is right. An empty template argument list, <> for a function template is allowed to be omitted ([temp.arg.explicit]/4). In other cases, the template argument list is generally required in order to name a particular specialization of a template, even if it is empty. See the grammar for simple-template-id, [temp.names]/1.

As a limited exception to the rule, if the name of a class template without a template argument list appears in a context where a concrete type is required, it is known as a "placeholder for a deduced class type" and this is only allowed in specific contexts listed in [dcl.type.class.deduct]. The most common one is a variable declaration like std::pair p("foo", 1) where the compiler will deduce std::pair<const char*, int> in C++17 and later.

In your code, you are trying to refer to a particular specialization of class template Test, without specifying the template argument list, and not in a context where the template arguments can be deduced. Therefore, it's not allowed.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
1

The new Class template argument deduction (CTAD) (since C++17) is applied to declarations. The expression foo<Test<>>(t); is not a declaration, it is a template function call.

273K
  • 29,503
  • 10
  • 41
  • 64
1

In your first code snippet, you specify a class template (Test) as a template parameter to foo and not a type (like an instance of a class template). You would need a function that takes a template template parameter to handle that.

Example:

#include <type_traits>

template<template<class> class T, class U> // T = Test, U = deduced to int
void foo(T<U>& bar) {
    std::cout << std::is_same<T<U>, Test<int>>::value << '\n'; // true
}

int main() {
    Test t;
    foo<Test>(t); // now ok
}

In your second snippet, foo<Test<>>(t); instantiates foo<Test<int>>(Test<int>&); since <> makes the template instantiation use the default type for the template parameter, which is int.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 1
    This sounds reasonable and works: https://godbolt.org/z/1vKf4x1Wr But with foo>(t) it doesn't https://godbolt.org/z/1vKf4x1Wr That surprises and confuses me. – Radrich Mar 31 '22 at 10:46
  • @Radrich You still need the overload that takes a _type_ if you want both to work. [example](https://godbolt.org/z/zGTrbYEhT) – Ted Lyngmo Mar 31 '22 at 10:52