【问题标题】:Is it possible for a function to only accept a limited set of types for a given argument?函数是否有可能只接受给定参数的有限类型集?
【发布时间】:2014-06-03 15:09:13
【问题描述】:

我知道如何使用模板处理任何数据类型:

template<typename T>
T myFunc(T data) { ... }

但是有没有办法将允许的类型集缩小到例如intcharstd::stringstd::wstring,所以编译器在遇到不允许的参数类型时会抛出错误,我会在编译时而不是运行时出错?

编辑:非常感谢 ecatmur,现在我理解了整个概念。

template<typename itemA_type, typename itemB_type>
typename std::enable_if<
  (
    std::is_same<itemA_type, int>::value ||
    std::is_same<itemA_type, char>::value) &&
  (
    std::is_same<itemB_type, std::string>::value ||
    std::is_same<itemB_type, std::wstring>::value ||
    std::is_same<itemB_type, const char*>::value ||
    std::is_same<itemB_type, const wchar_t*>::value
  ) ,
  void
>::type
myFunction(itemA_type itemA, itemB_type itemB) {
  using namespace std;
  cout << itemA << itemB << endl;
}

【问题讨论】:

  • 是否可以使用 C++11 的特性?
  • 这可以通过以下DeadMG 描述的任何方式以临时方式完成。有关库解决方案,请参阅Boost.ConceptCheck(以及其他)。
  • 为什么是模板,如果你可以用四种所需的参数类型重载函数???
  • @Massa One 可以使用重载,但在这种情况下,应该执行很多复制+粘贴操作。

标签: c++ function templates c++11 polymorphism


【解决方案1】:

采用这个实用程序特征类:

template<typename T, typename U, typename... Us>
struct is_any_of
    : std::integral_constant<
        bool,
        std::conditional<
            std::is_same<T,U>::value,
            std::true_type,
            is_any_of<T,Us...>
        >::type::value
      >
{ };

template<typename T, typename U>
struct is_any_of<T,U> : std::is_same<T,U>::type { };

然后你可以在静态断言中使用它:

template<typename T>
T myFunc(T data)
{
    static_assert( is_any_of<T, int, char, std::string>{}, "T not allowed");
}

如果您觉得更合适,可以使用std::is_convertiblestd::is_constructible 代替std::is_same

Live example.

【讨论】:

  • 我可以看到您禁用了将myFuncintcharstd::string 类型一起使用的可能性,不是吗?
  • @Constructor No. static_assert 行说 “T 应该是 int、char 或 string,否则我们会死。”
【解决方案2】:

使用enable_ifis_same 的简单解决方案:

template<typename T>
typename std::enable_if<
    std::is_same<T, int>::value ||
    std::is_same<T, char>::value,
    T>::type
myFunc(T data) { ... }

随着T 上的谓词变得更加复杂(例如,您是否只允许stringwstring,或basic_string 的其他特化?)您可能开始想要编写更复杂的谓词元函数;但现在,一个简单的表达式可能就足够了。

【讨论】:

  • 它对我不起作用:错误:'myFunc' 声明中有两种或多种数据类型
  • @rsk82 您在前一行遗漏了一个分号。
  • 好的,我试图通过在函数名之前添加返回类型声明来纠正它......奇怪,没有它它正在工作......所以我把这个函数的返回类型放在哪里?
  • @rsk82 你把它作为第二个参数放在enable_if 参数列表中——在我的例子中我把T 放在那里。
  • @rsk82 了解enable_if的使用方法,请参阅stackoverflow.com/questions/18977799/…
【解决方案3】:

您可以使用 SFINAE 做到这一点:

#include <type_traits>

template<class T> struct AllowedType : std::false_type {};
template<> struct AllowedType<int> : std::true_type {}; // Add more specializations.
template<> struct AllowedType<char> : std::true_type {}; // Add more specializations.

template<typename T>
typename std::enable_if<AllowedType<T>::value, T>::type
myFunc(T data);

int main() {
    myFunc(1);   // int, okay
    myFunc('c'); // char, okay
    myFunc(1.);  // double, fail
}

或者你可以指定允许的类型集合为boost::mpl::vector&lt;&gt;序列(效果同上):

#include <boost/mpl/vector.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/utility/enable_if.hpp>

typedef boost::mpl::vector<int, char> allowed_type_set;

template<typename T>
typename boost::enable_if<boost::mpl::contains<allowed_type_set, T>, T>::type
myFunc(T data);

int main() {
    myFunc(1);   // int, okay
    myFunc('c'); // char, okay
    myFunc(1.);  // double, fail
}

【讨论】:

  • 我无法理解……现在我把它读成了魔法。您可以在不使用 boost 的情况下为我举 intchar 的例子吗?也许这样我就能明白每个关键字的作用了。
  • @rsk82 查看我的第一个代码 sn-p,它允许 intchar
【解决方案4】:

Trait 类需要大量输入,而 boost 是一个重度依赖项,只是为了获得一个简单的类型列表。我会为此实现一个简单的可变参数类型列表模板:

template <typename...>
struct typelist {
    template <typename U>
    static constexpr bool contains() {
        return false;
    }
};

template <typename Head, typename...Tail>
struct typelist<Head, Tail...> {
    template <typename U>
    static constexpr bool contains() {
        return std::is_same<Head, U>::value || typelist<Tail...>::template contains<U>();
    }
};

所以myFunc 变成:

template<typename T>
typename std::enable_if<typelist<int,char,std::string>::contains<T>(), T>::type
myFunc(T data);

See it live at Coliru.


拥有 Concepts Lite 会很好,这样我们就可以编写:
template <typename T, typename...Types>
concept bool InList() {
    return typelist<Types...>::template contains<T>();
}

template<InList<int,char,std::string> T>
T myFunc(T data);

使用语法要简单得多。

【讨论】:

  • 为什么不返回std::integral_constant&lt; bool, blah &gt;?然后您可以标记调度而不是 SFINAE。 (或使用operator bool() constexpr 为SFINAE 提取它)。当结果可能是运行时计算的结果或参数的非类型值时,最好使用constexprbools。
  • @Yakk 我喜欢typelist&lt;...&gt;::contains&lt;...&gt;() 的可读性。我发现constexpr bool 函数提供了一个非常灵活的原语,可用于 SFINAE,或调度 std::integral_constant&lt;bool, typelist&lt;...&gt;::contains&lt;...&gt;()&gt;,甚至是 Concepts Lite,其中这将是简单的 template &lt;typename T&gt; requires typelist&lt;int,char,std::string&gt;::contains&lt;U&gt;() T myFunc(T data); 我认为 constexpr 函数对于大多数程序员来说更容易理解,因为它们模仿“普通”C++ 的命令式风格,而不是元编程的 FP 风格。
  • @Yakk 这是一个折衷方案,我认为在这种情况下,一个具有可读 SFINAE 条件的重载比带有标签调度的多个重载要好。
  • 保持相同的typelist&lt;...&gt;::contains&lt;...&gt;() 语法,但contains 返回std::true_typestd::false_type,而不是bool。有一个constexprbool 的转换,所以它适用于大多数需要bool 的情况,并且它可以通过标签调度使用。基返回std::false_typereturn {};,专门将逻辑移动到返回类型(std::integral_constant&lt;bool, logic&gt;)和return {};
  • @Yakk 是的,我知道这一切。我再说一遍,我们这些熟悉元编程的人都被误导了,认为将所有内容都包装在类型和重载中是个好主意。谓词本质上是类型的布尔函数;为什么用std::integral_constant&lt;bool, ...&gt; 而不是简单的bool 来混淆它?
【解决方案5】:
template<class...>struct types{typedef types type;};
template<class T, class types>struct contains:std::false_type {};
template<class T, class T0, class... Ts>struct contains<T, types<T0, Ts...>>:
  std::integral_constant<bool,
    std::is_same<T, T0>::value || contains<T, types<Ts...>>::value
  >
{};

使用上述,我们可以使用 SFINAE 或标签调度。您可能还想考虑std::is_convertible 而不是is_same

typedef types<int,double, std::string> allowed_types;

template<typename T, typename=typename std::enable_if< contains<T, allowed_types>::value >::type>
T myFunc(T data) {
  // code
}

或标签调度:

namespace details {
  template<typename T>
  T myFunc(T data, std::true_type) {
    // code
  }
}
template<typename T>
T myFunc(T data) {
  return details::myFunc(std::forward<T>(data), contains<T, allowed_types>{});
}

它会产生很好的错误并允许您轻松编写后备版本,或者通过static_assert 来编写:

template<typename T>
T myFunc(T data) {
  static_assert(contains<T, allowed_types>{}, "type not allowed");
  // code
}

所有这些都有不同的优点。虽然 SFINAE 看起来很诱人,但我建议不要这样做:它很脆弱,会产生疯狂的错误消息。

【讨论】:

  • Bikeshed:contains&lt;T, allowed_types&gt; 似乎在询问T 是否包含allowed_types,这让我很困扰。我认为contains&lt;allowed_types, T&gt;is_in&lt;T, allowed_types&gt; 会更具可读性。
【解决方案6】:

您可以使用重载、SFINAE、标签分派或特化来实现此目的。都可以产生你想要的结果。

【讨论】:

  • 还有C++11显式模板实例化声明+定义。
  • 可以那样做,但几乎没有人会这样做,因为这很麻烦。
  • 感谢您的评论。 :-)
猜你喜欢
  • 2012-01-27
  • 1970-01-01
  • 2011-11-12
  • 1970-01-01
  • 1970-01-01
  • 2018-12-30
  • 2020-12-26
  • 2019-12-14
  • 1970-01-01
相关资源
最近更新 更多