简体   繁体   English

用std :: enable_if重载函数以避免模板替换错误

[英]Overloading a function with std::enable_if to avoid template substitution error

I want to write two template functions such that one catches a specific case and the other catches all other cases that don't match first case. 我想编写两个模板函数,以便一个捕获特定情况,另一个捕获与第一个情况不匹配的所有其他情况。 I'm trying to use std::enable_if to catch the specific case, but the compiler still fails with an ambiguous match. 我正在尝试使用std :: enable_if来捕获特定情况,但是编译器仍然因模棱两可的匹配而失败。 How can I write these overloaded functions so that the ambiguity is resolved by the compiler? 如何编写这些重载函数,以便编译器解决歧义? (I'm using g++) (我正在使用g ++)

I've tried writing the following code (this is a simplified example that reproduces the problem): 我尝试编写以下代码(这是一个重现该问题的简化示例):

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]);
   }
}

I expected the compiler to match all cases where std::is_same_v<U::resource_t,R> to the specific case and all other combinations to the general case, instead, the compiler fails saying the function is ambiguous. 我希望编译器将std::is_same_v<U::resource_t,R>的所有情况与特定情况匹配std::is_same_v<U::resource_t,R>并将所有其他组合与一般情况相匹配,相反,编译器无法说出该函数是模棱两可的。 I also tried ! std::is_same 我也试过了! std::is_same ! std::is_same for the second definition and the compiler fails with error: redefinition of ... callCalcValue()... ! std::is_same用于第二个定义,编译器失败,并显示error: redefinition of ... callCalcValue()...

Here's a way reduced example: 这是一个简化的示例:

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{});

It doesn't really matter what the particular sfinae constraint is, so I picked a simple one. sfinae约束到底是什么并不重要,所以我选择了一个简单的约束。 Now, if the constraint (in this case is_same_v<R, int> ) is not met, substitution into #1 fails and we're left with one single candidate: #2 . 现在,如果约束(在这种情况下is_same_v<R, int>被满足,代入#1失败,并且我们留下与一个单一候选: #2

But if substitution succeeds, then we have two candidates. 但是,如果替代成功,那么我们就有两个候选人。 They're equivalently viable, there's nothing to distinguish them. 它们具有同等的可行性,没有任何区别。 Hence, the call is ambiguous! 因此,通话不明确!


We need an additional way to distinguish them. 我们需要一种其他方法来区分它们。 One way is to add the negated constrained to the other overload (note that you need to change the SFINAE form here to make this work): 一种方法是将取反的约束添加到另一个重载中(请注意,您需要在此处更改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

This ensures exactly one of the two is viable. 这样可以确保两者中的一个完全可行。

Another way would be to forward the trait to a separate overload set: 另一种方法是将特征转发到单独的重载集:

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
}

Another way would be to just write one overload and use if constexpr internally: 另一种方法是只编写一个重载并if constexpr内部使用if constexpr

template <typename T, typename R>
void foo(T, R) {
    if constexpr (std::is_same_v<R, int>) {
        // #1
    } else {
        // #2
    }
}

A future way (in C++20) would be to use concepts: 未来的方式(在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

This would make #1 "more constrained than" #2 if it's viable, so it would be preferred. 如果可行的话,这会使#1约束比#2约束更大,因此它是首选。

I wonder if a simple overload could work as well: 我想知道简单的重载是否也可以工作:

// 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);
      }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM