使用 std::enable_if 重载函数以避免模板替换错误
Overloading a function with std::enable_if to avoid template substitution error
我想编写两个模板函数,一个捕获特定情况,另一个捕获与第一个情况不匹配的所有其他情况。
我正在尝试使用 std::enable_if 来捕捉特定情况,但编译器仍然因不明确的匹配而失败。我如何编写这些重载函数,以便编译器解决歧义? (我正在使用 g++)
我试过编写以下代码(这是一个重现问题的简化示例):
struct resource1_t{
};
struct resource2_t{
};
template <typename R, typename V>
struct unit_t{
typedef R resource_t;
typedef V value_t;
unit_t(value_t const& value):v(){}
value_t v;
value_t calcValue(resource_t const& r)const{return v;}
};
// Specific case (U::resource_t == R)
template <typename U, typename R, typename=std::enable_if_t<std::is_same_v<typename U::resource_t,R>>>
typename U::value_t callCalcValue(U const& u, R const& r){
return u.calcValue(r);
}
// General case (U::resource_t != R)
template <typename U, typename R>
typename U::value_t callCalcValue(U const& u, R const& r){
// Fail immediately!
assert(!"Unit resource does not match");
return U::value_t();
}
int main()
{
// Create an array of unit variants
typedef unit_t<resource1_t,int> U1;
typedef unit_t<resource2_t,float> U2;
std::vector<std::variant<U1,U2>> units;
units.emplace_back(U1(1));
units.emplace_back(U2(1.0f));
// Create a parallel array of resources
std::vector<std::variant<resource1_t,resource2_t>> resources;
resources.emplace_back(resource1_t());
resources.emplace_back(resource2_t());
// Call calcValue for each unit on the parallel resource
for(int i(0); i<units.size(); ++i){
std::visit([&](auto&& unit){
std::visit([&](auto&& resource){
// Fails to compile with substitution failure...
//std::cout << unit.calcValue(resource) << "\n";
// Results in ambiguous call compile error...
std::cout << callCalcValue(unit,resource) << "\n";
},resources[i]);
},units[i]);
}
}
我希望编译器将所有 std::is_same_v<U::resource_t,R>
的情况匹配到特定情况,并将所有其他组合匹配到一般情况,相反,编译器没有说函数不明确。
我还尝试了 ! std::is_same
作为第二个定义,编译器失败并显示 error: redefinition of ... callCalcValue()...
这里有一个简化的例子:
template <typename T, typename R, typename=std::enable_if_t<std::is_same_v<R, int>>
void foo(T, R); // #1
template <typename T, typename R>
void foo(T, R); // #2
foo(some_t{}, some_r{});
具体的 sfinae 约束是什么并不重要,所以我选择了一个简单的约束。现在,如果约束(在本例中为 is_same_v<R, int>
)不满足,则替换为 #1
失败,我们只剩下一个候选者:#2
.
但是如果替换成功,那么我们有两个候选人。它们是同等可行的,没有什么可以区分它们。因此,调用是不明确的!
我们需要一种额外的方式来区分它们。一种方法是将否定约束添加到另一个重载(请注意,您需要在此处更改 SFINAE 形式才能使其工作):
template <typename T, typename R, std::enable_if_t<std::is_same_v<R, int>, int> = 0>
void foo(T, R); // #1
template <typename T, typename R, std::enable_if_t<!std::is_same_v<R, int>, int> = 0>
void foo(T, R); // #2
这确保两者之一可行。
另一种方法是将特征转发到单独的重载集:
template <typename T, typename R>
void foo_impl(T, R, std::true_type); // #1
template <typename T, typename R>
void foo_impl(T, R, std::false_type); // #2
template <typename T, typename R>
void foo(T t, R r) {
return foo_impl(t, r, std::is_same<R, int>{}); // NB: not _v
}
另一种方法是只写一个重载并在内部使用 if constexpr
:
template <typename T, typename R>
void foo(T, R) {
if constexpr (std::is_same_v<R, int>) {
// #1
} else {
// #2
}
}
未来的方式(在 C++20 中)将是使用概念:
template <typename T, typename R>
requires std::is_same_v<R, int>
void foo(T, R); // #1
template <typename T, typename R>
void foo(T, R); // #2
这将使 #1
"more constrained than" #2
如果可行,那么它是首选。
我想知道一个简单的重载是否也可以工作:
// General case (U::resource_t != R)
template <typename U, typename R>
typename U::value_t callCalcValue(U const& u, R const& r){
// Fail immediately!
assert(!"Unit resource does not match");
return U::value_t();
}
//Specific case (U::resource_t == R) overloads the general case
template <typename U>
typename U::value_t callCalcValue(U const& u, typename U::resource_t const& r){
return u.calcValue(r);
}
我想编写两个模板函数,一个捕获特定情况,另一个捕获与第一个情况不匹配的所有其他情况。 我正在尝试使用 std::enable_if 来捕捉特定情况,但编译器仍然因不明确的匹配而失败。我如何编写这些重载函数,以便编译器解决歧义? (我正在使用 g++)
我试过编写以下代码(这是一个重现问题的简化示例):
struct resource1_t{
};
struct resource2_t{
};
template <typename R, typename V>
struct unit_t{
typedef R resource_t;
typedef V value_t;
unit_t(value_t const& value):v(){}
value_t v;
value_t calcValue(resource_t const& r)const{return v;}
};
// Specific case (U::resource_t == R)
template <typename U, typename R, typename=std::enable_if_t<std::is_same_v<typename U::resource_t,R>>>
typename U::value_t callCalcValue(U const& u, R const& r){
return u.calcValue(r);
}
// General case (U::resource_t != R)
template <typename U, typename R>
typename U::value_t callCalcValue(U const& u, R const& r){
// Fail immediately!
assert(!"Unit resource does not match");
return U::value_t();
}
int main()
{
// Create an array of unit variants
typedef unit_t<resource1_t,int> U1;
typedef unit_t<resource2_t,float> U2;
std::vector<std::variant<U1,U2>> units;
units.emplace_back(U1(1));
units.emplace_back(U2(1.0f));
// Create a parallel array of resources
std::vector<std::variant<resource1_t,resource2_t>> resources;
resources.emplace_back(resource1_t());
resources.emplace_back(resource2_t());
// Call calcValue for each unit on the parallel resource
for(int i(0); i<units.size(); ++i){
std::visit([&](auto&& unit){
std::visit([&](auto&& resource){
// Fails to compile with substitution failure...
//std::cout << unit.calcValue(resource) << "\n";
// Results in ambiguous call compile error...
std::cout << callCalcValue(unit,resource) << "\n";
},resources[i]);
},units[i]);
}
}
我希望编译器将所有 std::is_same_v<U::resource_t,R>
的情况匹配到特定情况,并将所有其他组合匹配到一般情况,相反,编译器没有说函数不明确。
我还尝试了 ! std::is_same
作为第二个定义,编译器失败并显示 error: redefinition of ... callCalcValue()...
这里有一个简化的例子:
template <typename T, typename R, typename=std::enable_if_t<std::is_same_v<R, int>>
void foo(T, R); // #1
template <typename T, typename R>
void foo(T, R); // #2
foo(some_t{}, some_r{});
具体的 sfinae 约束是什么并不重要,所以我选择了一个简单的约束。现在,如果约束(在本例中为 is_same_v<R, int>
)不满足,则替换为 #1
失败,我们只剩下一个候选者:#2
.
但是如果替换成功,那么我们有两个候选人。它们是同等可行的,没有什么可以区分它们。因此,调用是不明确的!
我们需要一种额外的方式来区分它们。一种方法是将否定约束添加到另一个重载(请注意,您需要在此处更改 SFINAE 形式才能使其工作):
template <typename T, typename R, std::enable_if_t<std::is_same_v<R, int>, int> = 0>
void foo(T, R); // #1
template <typename T, typename R, std::enable_if_t<!std::is_same_v<R, int>, int> = 0>
void foo(T, R); // #2
这确保两者之一可行。
另一种方法是将特征转发到单独的重载集:
template <typename T, typename R>
void foo_impl(T, R, std::true_type); // #1
template <typename T, typename R>
void foo_impl(T, R, std::false_type); // #2
template <typename T, typename R>
void foo(T t, R r) {
return foo_impl(t, r, std::is_same<R, int>{}); // NB: not _v
}
另一种方法是只写一个重载并在内部使用 if constexpr
:
template <typename T, typename R>
void foo(T, R) {
if constexpr (std::is_same_v<R, int>) {
// #1
} else {
// #2
}
}
未来的方式(在 C++20 中)将是使用概念:
template <typename T, typename R>
requires std::is_same_v<R, int>
void foo(T, R); // #1
template <typename T, typename R>
void foo(T, R); // #2
这将使 #1
"more constrained than" #2
如果可行,那么它是首选。
我想知道一个简单的重载是否也可以工作:
// General case (U::resource_t != R)
template <typename U, typename R>
typename U::value_t callCalcValue(U const& u, R const& r){
// Fail immediately!
assert(!"Unit resource does not match");
return U::value_t();
}
//Specific case (U::resource_t == R) overloads the general case
template <typename U>
typename U::value_t callCalcValue(U const& u, typename U::resource_t const& r){
return u.calcValue(r);
}