C++17 template класс со значением аргумента по умолчанию

Сразу прошу прощения, я не знаю что написать в заголовок, ниже я дал минимальный пример кода. Я использую компилятор MSVC C++17. Объясните, почему для некоторых сущностей нужно писать <> при инициализации шаблона, а для некоторых можно опустить (в случае, если для всех аргументов есть значение по умолчанию)? Например, я могу вызвать шаблонную функцию, или создать экземпляр шаблонного класса внутри функции без <>, но если я хочу объявить член класса, то я обязан использовать A<> a вместо A a.

#include <iostream>

template<size_t i = 1>
class A {
public:
    size_t foo() {
        return i;
    }
};

template<size_t i = 1>
size_t foo() {
    return i;
}

class B {
public:
    A a; // error
    A<> a; // okey

    static size_t foo1() {
        return foo(); // okey
    }
    template<size_t i = 2>
    static size_t foo2() {
        return foo(); // okey
    }
};

int main() {
    A a; // okey
    auto &f1 = foo; // error
    auto &f1 = foo<>; // okey
    auto &f2 = B::foo1; // okey
    auto &f3 = B::foo2; // error
    auto &f3 = B::foo2<>; // okey

    return B().a.foo() + A().foo(); // okey
}

P.S. на godbolt другие компиляторы имели то же поведение, т.е., возможно, это какой-то стандарт, но я не знаю как загуглить.


Ответы (1 шт):

Автор решения: HolyBlackCat

Возможность не писать пустые <> для класса - это частный случай более крупной фичи - возможности выводить шаблонные параметры из аргументов конструктора (CTAD = class template argument deduction).

Поэтому разрешено это плюс-минус только там, где есть какой-то вызов конструктора (есть из чего выводить параметры).

Например так нельзя: std::vector foo();. А так можно: std::vector v = {1,2,3};.

Тогда почему в нестатическом члене класа нельзя? Потому что это бы странно работало. Инициализатор, который там подписан, можно переопределить в списке инициализации в конструкторе. К примеру, вот это заслуженно не компилируется:

struct A
{
    std::vector a = {1,2,3}; // Допустим T = int.

    A()
        : a{1.0, 2.0, 3.0} // Из-за этого {1,2,3} полностью игнорируется.
        // Что дальше? Оставляем T = int? Не менять же на double задним числом?
    {} 

};
→ Ссылка