【问题标题】:correct approach to conditionally return different types from a template function从模板函数有条件地返回不同类型的正确方法
【发布时间】:2016-09-13 10:17:14
【问题描述】:

我有一个通常返回整数的函数,但由于值的语义可能不同,我想对它们进行强类型,所以我引入了两种类型,例如金钱和时间,简化为

struct Money {
   uint32_t value;
}

该函数将根据 bool 参数返回 Money 或 Time。假设它看起来像这样:

template <typename T> T getValue(bool mode) {
  Money money;
  Time time;
  ...
  if (mode == ModeMoney) {
   money = something * 2;//get it from somewhere - irrelevant for the example
   return money;
  }
  if (mode == ModeTime) {
   time = something * 100;
   return time;
  }
}

现在编译器会抱怨不同的返回类型,所以我添加了专门的模板函数来返回值本身:

template <> Money variableValue<Money>(something) { return something * 2 };
template <> Time variableValue<Time>(something) { return something * 100};

这允许在调用时删除 bool 参数,主函数现在将更改为:

template <typename T> T getValue(bool mode) {
  ....//calculation of *something* is the same, we only need different output from the function
  return variableValue<T>(something);
}

这是一个好方法吗?

【问题讨论】:

  • 如果你的函数有不同的逻辑和不同的返回类型,为什么不把它拆分成两个函数呢?
  • 用例是什么?根据您使用getValue 的方式,有几种替代方案可能会更好
  • 我发布的示例非常简化,这可能会造成混淆。逻辑其实是一样的,写两个函数只会复制两个不同输出的逻辑

标签: c++ templates


【解决方案1】:

根据 Ami 的评论,我建议您制作两个函数和一个助手:

Time getTime() { return calculateSomething() * 100; }
Money getMoney() { return calculateSomething() * 2; }

将不同的用例分开;模板的主要目的是使接口更简单,而不是实现。但是,如果您使用模板将看起来应该是两个功能的东西变成一个,那不会使界面更简单,因为它不直观。 (正如您的问题所示,它实际上也没有使实现更简单。)

【讨论】:

    【解决方案2】:

    作为替代解决方案,您可以使用特征类来执行此操作并避免重复代码。
    它遵循一个最小的例子:

    template<typename>
    struct traits;
    
    template<>
    struct traits<Money> {
        using return_type = Money;
        static constexpr std::size factor = 2;
    };
    
    template<>
    struct traits<Time> {
        using return_type = Time;
        static constexpr std::size factor = 100;
    };
    
    template <typename T>
    traits<T>::return_type getValue() {
        traits<T>::return_type ret;
        // ...
        ret = something * traits<T>::factor;
        return ret:
    }
    

    是否合适的解决方案主要取决于getValue的真实代码和实际实现。

    【讨论】:

      【解决方案3】:

      除了关于设计问题的问题,您似乎想要一个标记的联合或变体variant&lt;Time, Money&gt;。下一个 C++ 标准将具有 variant class in the standard library,但在此可用之前,您可以使用 Boost.Variant。有了这个,代码看起来像

      std::variant<Money, Time> getValue(bool mode) {
          if (mode) {
              return std::variant<Money, Time>( Money{23} );
          }
          else {
              return std::variant<Money, Time>( Time{42} );        
          }
      }
      
      auto m = std::get<Money>( getValue(true) );
      auto m2 = std::get<Money>( getValue(false) );
      assert( m != nullptr && m2 == nullptr );
      

      您也可以仅通过使用联合来实现自己的金钱和时间变体:

      struct TimeOrMoney
      {
          enum class Type {isTime, isMoney};
          Type type;
          union
          {
              Time time;
              Money money;
          }
      }
      

      另一种方法可能是返回std::pair&lt; boost::optional&lt;Money&gt;, boost::optional&lt;Time&gt; &gt;,然后检查实际设置了两者中的哪一个。这使用特殊的可选状态作为标记来指示已设置的值。

      【讨论】:

      • 啊,对了! std::variant 将是最优雅的解决方案;但尚不可用,并且无法使用 boost。另一方面,struct+union 的想法似乎很简洁
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-11
      • 1970-01-01
      • 1970-01-01
      • 2022-01-19
      • 2023-02-22
      • 2017-11-14
      相关资源
      最近更新 更多