函数模板参数推导中的编译器差异
Compiler variance in function template argument deduction
以下程序:
#include <type_traits>
template<typename T, bool b>
struct S{
S() = default;
template<bool sfinae = true,
typename = std::enable_if_t<sfinae && !std::is_const<T>::value>>
operator S<T const, b>() { return S<T const, b>{}; }
};
template<typename T, bool b1, bool b2>
void f(S<const std::type_identity_t<T>, b1>,
// ^- T in non-deduced context for func-param #1
S<T, b2>)
// ^- T deduced from here
{}
int main() {
S<int, true> s1{};
S<int, false> s2{};
f(s1, s2);
}
被 GCC (11.2) 接受,但被 Clang (13) 和 MSVC (19.latest) 拒绝,全部用于 -std=c++20
/ /std:c++20
(DEMO)。
- 这里哪个编译器是正确的?
这由 [temp.deduct.call], particularly /4 管理:
In general, the deduction process attempts to find template argument values that will make the deduced A
identical to A
(after the type A
is transformed as described above). However, there are three cases that allow a difference: [...]
在 OP 的示例中,A
是 S<const int, true>
并且(转换后的)A
是 S<int, int>
,并且 none 这 [三个] 案例适用于此处,表示推导失败。
我们可以通过调整程序来对此进行试验,使推导的 A
与转换后的 A
差异属于三种情况之一;说 [temp.deduct.call]/4.3
- If
P
is a class and P
has the form simple-template-id, then the transformed A
can be a derived class D
of the deduced A
. [...]
#include <type_traits>
template<typename T, bool b>
struct S : S<const T, b> {};
template<typename T, bool b>
struct S<const T, b> {};
template<typename T, bool b1, bool b2>
void f(S<const std::type_identity_t<T>, b1>, S<T, b2>){}
int main() {
S<int, true> s1{};
S<int, true> s2{};
f(s1, s2);
}
该程序被所有三个编译器正确接受 (DEMO)。
因此,GCC 很可能在这里有错误,因为上面争论的错误是可以诊断的(不是格式错误的 NDR)。由于找不到问题的开放错误,我提交了:
- Bug 103333 - [accepts-invalid] 不兼容 'transformed A' / 'deduced A' 对
的函数模板参数推导
我们可能还注意到 [temp.arg.explicit]/7 包含一个特殊情况,允许隐式转换将参数类型转换为函数参数类型:
Implicit conversions ([conv]) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction.
这 不 适用于 OP 的示例,但是,因为(函数)参数类型 S<const std::type_identity_t<T>, b1>
还包含(非类型)模板参数 b1
,参与模板参数推导。
但是在下面的程序中:
#include <type_traits>
template<typename T>
struct S{
S() = default;
template<bool sfinae = true,
typename = std::enable_if_t<sfinae && !std::is_const<T>::value>>
operator S<T const>() { return S<T const>{}; }
};
template<typename T>
void f(S<const std::type_identity_t<T>>, S<T>) {}
int main() {
S<int> s1{};
S<int> s2{};
f(s1, s2);
}
(函数)参数类型是A<std::type_identity_t<T>>
,其中唯一的模板参数是T
,它不参与该参数-参数对的模板参数推导(P
/A
)。因此,[temp.arg.explicit]/7 确实适用于此并且程序格式正确。
以下程序:
#include <type_traits>
template<typename T, bool b>
struct S{
S() = default;
template<bool sfinae = true,
typename = std::enable_if_t<sfinae && !std::is_const<T>::value>>
operator S<T const, b>() { return S<T const, b>{}; }
};
template<typename T, bool b1, bool b2>
void f(S<const std::type_identity_t<T>, b1>,
// ^- T in non-deduced context for func-param #1
S<T, b2>)
// ^- T deduced from here
{}
int main() {
S<int, true> s1{};
S<int, false> s2{};
f(s1, s2);
}
被 GCC (11.2) 接受,但被 Clang (13) 和 MSVC (19.latest) 拒绝,全部用于 -std=c++20
/ /std:c++20
(DEMO)。
- 这里哪个编译器是正确的?
这由 [temp.deduct.call], particularly /4 管理:
In general, the deduction process attempts to find template argument values that will make the deduced
A
identical toA
(after the typeA
is transformed as described above). However, there are three cases that allow a difference: [...]
在 OP 的示例中,A
是 S<const int, true>
并且(转换后的)A
是 S<int, int>
,并且 none 这 [三个] 案例适用于此处,表示推导失败。
我们可以通过调整程序来对此进行试验,使推导的 A
与转换后的 A
差异属于三种情况之一;说 [temp.deduct.call]/4.3
- If
P
is a class andP
has the form simple-template-id, then the transformedA
can be a derived classD
of the deducedA
. [...]
#include <type_traits>
template<typename T, bool b>
struct S : S<const T, b> {};
template<typename T, bool b>
struct S<const T, b> {};
template<typename T, bool b1, bool b2>
void f(S<const std::type_identity_t<T>, b1>, S<T, b2>){}
int main() {
S<int, true> s1{};
S<int, true> s2{};
f(s1, s2);
}
该程序被所有三个编译器正确接受 (DEMO)。
因此,GCC 很可能在这里有错误,因为上面争论的错误是可以诊断的(不是格式错误的 NDR)。由于找不到问题的开放错误,我提交了:
- Bug 103333 - [accepts-invalid] 不兼容 'transformed A' / 'deduced A' 对 的函数模板参数推导
我们可能还注意到 [temp.arg.explicit]/7 包含一个特殊情况,允许隐式转换将参数类型转换为函数参数类型:
Implicit conversions ([conv]) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction.
这 不 适用于 OP 的示例,但是,因为(函数)参数类型 S<const std::type_identity_t<T>, b1>
还包含(非类型)模板参数 b1
,参与模板参数推导。
但是在下面的程序中:
#include <type_traits>
template<typename T>
struct S{
S() = default;
template<bool sfinae = true,
typename = std::enable_if_t<sfinae && !std::is_const<T>::value>>
operator S<T const>() { return S<T const>{}; }
};
template<typename T>
void f(S<const std::type_identity_t<T>>, S<T>) {}
int main() {
S<int> s1{};
S<int> s2{};
f(s1, s2);
}
(函数)参数类型是A<std::type_identity_t<T>>
,其中唯一的模板参数是T
,它不参与该参数-参数对的模板参数推导(P
/A
)。因此,[temp.arg.explicit]/7 确实适用于此并且程序格式正确。