【问题标题】:Overloading a function with std::enable_if to avoid template substitution error使用 std::enable_if 重载函数以避免模板替换错误
【发布时间】:2019-04-17 14:18:04
【问题描述】:

我想编写两个模板函数,一个捕获特定情况,另一个捕获与第一种情况不匹配的所有其他情况。 我正在尝试使用 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&lt;U::resource_t,R&gt; 的所有情况与特定情况以及所有其他组合与一般情况相匹配,相反,编译器无法说明函数不明确。 我还为第二个定义尝试了! std::is_same,编译器失败并显示error: redefinition of ... callCalcValue()...

【问题讨论】:

    标签: c++ c++17 enable-if std-variant


    【解决方案1】:

    这是一个简化的例子:

    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&lt;R, int&gt;),则替换为#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“比”#2“更受约束”,所以它会是首选。

    【讨论】:

    • 使用 !std::is_same_v 对于我的具体情况仍然会导致编译错误。但是,您使用 constexpr 的第二个示例有效!
    • @thayne 是的,那是因为我写错了,已修复。但是,是的,if constexpr 如果可行,那就更好了。
    【解决方案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);
          }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-01
      • 1970-01-01
      • 2014-01-06
      • 2017-05-28
      • 2020-05-30
      • 2020-11-10
      • 1970-01-01
      相关资源
      最近更新 更多