【问题标题】:Template type deduction in parameter and return type参数和返回类型中的模板类型推导
【发布时间】:2021-01-26 11:55:17
【问题描述】:

在我们当前的代码库中,我们的代码如下所示(这当然是简化版):

#include <map>
#include <string>
#include <iostream>

using namespace std;

// General template function to convert an enum to a string, given a converter-container
// The converter-container is either a map, or a vector/array etc. of pair, 
// e.g. something that has enum in .first and string in .second

template<typename Container, typename Index>
string toString(Container&& names, Index&& index, const string& noMatch = "NoMatch")
{
    for (auto&& elem : names) {
      if (elem.first == index) {
         return elem.second;
      }
   }
   return noMatch;
}


// Simple example enum
enum class Color
{
    red, green, blue, pink
};

// Example of two converter-container types
constexpr array colors1 = {pair{Color::red, "Red"}, pair{Color::green, "Green"}, pair{Color::blue, "Blue"}};
const map<Color, string> colors2  =  {{Color::red, "Red"}, {Color::green, "Green"}, {Color::blue, "Blue"}};


int main()
{
    cout << toString(colors1, Color::red) << " " << toString(colors2, Color::blue) << " "
     << toString(colors2, Color::pink) << endl;     
}

此代码按预期工作,并打印出“Red Blue NoMatch”

但是,我想重写 toString 函数,使其不返回字符串,而是返回存储在转换容器中的任何类型(例如 string、string_view 或 char*)。

如果没有 noMatch 部分,任务会很简单,只需使用“auto”而不是“string”作为返回类型:

template<typename Container, typename Index>
auto toString(Container&& names, Index&& index)
{
    for (auto&& elem : names) {
      if (elem.first == index) {
         return elem.second;
      }
   }
   return begin(names)->second;
}

这工作并打印出“红蓝红”,但不是我正在寻找的解决方案。我寻找的是一种拥有的方式:

template<typename Container, typename Index>
auto toString(Container&& names, Index&& index, const <returnType>& noMatch = "")
{
    for (auto&& elem : names) {
      if (elem.first == index) {
         return elem.second;
      }
   }
   return noMatch;
}

但正如你所见,我需要为 填写一些内容,但我不知道会是什么。 如果有帮助的话,我可以将函数分成两个不同的函数,一个用于映射,另一个用于数组/向量对,但我希望能够保持相同的调用语法。

FWIW,我们目前使用启用了 c++17 的 gcc 9.2。

谢谢。

【问题讨论】:

  • 无论返回类型是什么,它在所有分支中都必须相同。在你的情况下它是 decltype(declval<:value_type>().second).

标签: c++ c++17


【解决方案1】:

只需推断second的类型,例如:

template<typename Container, typename Index,
         typename NoMatchType=decltype(std::declval<typename Container::value_type &&>().second)>
auto toString(Container&& names, Index&& index,
              const NoMatchType &noMatch=NoMatchType{})

(如果您愿意,也可以在此处使用NoMatchType 代替auto

【讨论】:

  • 我认为应该是declval&lt;typename Container::value_type&gt;().second
  • #include &lt;string&gt;#include &lt;utility&gt;了吗?
  • 感谢@StPiere 和@SamVarshavchik,但两者都不起作用。这两个建议都在“declval”上给出错误:error: 'const std::map&lt;Color, std::__cxx11::basic_string&lt;char&gt; &gt;&amp;' is not a class, struct, or union type
  • 您可能缺少#include &lt;map&gt;。但是如果你能分享一下godbolt链接会更容易
  • 我的答案可能需要调整,但是如果您了解此处显示的基本解决方案,您应该可以自己找出必要的调整。你熟悉declvaldecltype等吗?等等,这个语法试图做什么?
【解决方案2】:

我发布了对@Sam 答案的改进,但归结为同一件事:

您可以使用一些相对容易理解的类型特征来实现您想要的,而不是第三个模板参数(这会使您的界面复杂化并锁定它以防止以后的简单扩展)。 而不是decltype/std::declval 语法对于它想要做的事情来说有点繁重,即获取Containervalue_type 的第二部分的类型。我们可以使用pairTupleLike 品质来简化这一点:

template<typename Container, typename Index>
auto toString(Container&& names, Index&& index,
              const std::tuple_element_t<1, typename Container::value_type>& noMatch = {})

注意我省略了“重”参数类型的重复(在原始版本中已经不需要)。 如果你知道你只会对Container 使用MapLike 类型,你可以走捷径直接使用它的类型别名:

template<typename Container, typename Index>
auto toString(Container&& names, Index&& index,
              const typename Container::mapped_type& noMatch = {})

【讨论】:

  • 谢谢@rubenvb。经过一番努力,我得到了您的上述解决方案(这很好,因为这与其他任何事情一样都是一种学习经验):godbolt.org/z/6aYTs7 我需要做的是 1)在容器上使用 remove_reference_t - 和然后从 0 而不是 1 计数 - 从而在使用 tuple_element_t 时将索引从 2 更改为 1 ;-)
  • 啊,是的,索引是我的错误,对此感到抱歉。 remove_reference_t 是必要的,因为您使用推断出的Container&amp;&amp;(并且没有错误或任何东西!)但是如果您使用例如const Container&amp; 然后remove_reference_t 就没有必要了。您在这些方面获得的经验越多,您在编写此类代码时就可以更好地解决您面临的问题。您也许可以避免一些问题,但您将继续遇到问题。很高兴你能从中学到东西!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-11
  • 1970-01-01
  • 2015-03-12
相关资源
最近更新 更多