Isso é (parcialmente) reduzido de CTAD instancia outra especialização quando argumento explícito é fornecido .
https://godbolt.org/z/5To7nKEP3
template<int N>
struct Bar {
int n = N;
constexpr Bar() {}
constexpr Bar(const Bar& b): n{b.n - 1} {}
};
template<Bar b> constexpr int get_n() { return b.n; }
constexpr auto b = Bar<1>{};
static_assert(b.n == 1);
#ifdef _MSC_VER
static_assert(get_n<b>() == 1);
#else
static_assert(get_n<b>() == 0);
#endif
Clang e GCC parecem sempre invocar e deduzir dos Bar
construtores de para determinar o argumento do modelo real , mesmo que o argumento fornecido seja do tipo de especialização de Bar
, com o que o MSVC não concorda.
O que diz a norma?
O programa deve estar malformado para qualquer um dos
static_asserts
.Primeiro, note que isso não tem nada a ver com CTAD. Se você especificar explicitamente
Bar<1>
como tipo de parâmetro de modelo, você ainda terá o mesmo comportamento do compilador.A dedução do argumento do modelo de classe é sempre feita e será deduzida
Bar<1>
aqui.Entretanto, a questão agora é como o objeto de parâmetro de modelo é inicializado a partir do argumento de modelo para determinar a especialização relevante
get_n
que será chamada.Como exatamente isso deveria acontecer para tipos de classe não foi especificado, veja o problema 2459 do CWG . Acho que é por isso que você vê divergência do compilador no seu exemplo.
A resolução do problema com o documento P2308R1 no final de 2023 faz com que a chamada
get_n<b>()
fique mal formada.Basicamente, as novas regras estabelecem, aplicadas ao seu exemplo, que começamos imaginando uma variável definida como
E então imaginamos ainda que o objeto de parâmetro de modelo (também do tipo
Bar<1>
como deduzido anteriormente) é inicializado por cópia da expressãov
. Se então o objeto de parâmetro de modelo não for equivalente a argumento de modelov
(ou seja, eles têm o "mesmo" valor (e tipo)), então o programa é malformado.Isso acontece no seu caso porque o construtor de cópia (que é usado na inicialização
v
e na inicialização de cópia do objeto de parâmetro de modelo) produzirá um valor diferente no novo objeto do que no objeto de origem.Parece que nenhum dos compiladores implementou isso ainda.