为使用数组,向量,结构等传递给variadic函数或可变参数模板函数的所有参数指定一种类型?

问题描述 投票:36回答:10

我正在创建一个函数(可能是成员函数,而不是它很重要......也许它确实?)需要接受未知数量的参数,但我希望它们都是相同的类型。我知道我可以传入数组或向量,但我希望能够直接接受args列表而无需额外的结构甚至是额外的括号。看起来variadic函数本身并不是类型安全的,我不知道如何使用这个w / variadic模板函数。这基本上就是我的目标(更可能不是正确的代码,完全不是为了获取龙的列表,哈哈):

//typedef for dragon_list_t up here somewhere.

enum Maiden {
    Eunice
    , Beatrice
    , Una_Brow
    , Helga
    , Aida
};

dragon_list_t make_dragon_list(Maiden...) {
    //here be dragons
}

要么

template<Maiden... Maidens> dragon_list_t make_dragon_list(Maidens...) {
    //here be dragons
}

用法

dragon_list_t dragons_to_slay
    = make_dragon_list(Maiden.Eunice, Maiden.Helga, Maiden.Aida)
;

尝试了类似于上面的一些事情,没有骰子。建议?我可能做出明显的疏忽?我知道这样做可能不是一件大事:

dragon_list_t make_dragon_list(std::array<Maiden> maidens) {
    //here be dragons.
}
dragon_list_t dragons_to_slay
    = make_dragon_list({Maiden.Eunice, Maiden.Helga, Maiden.Aida})
;

但如果可能的话,我宁愿能够以第一种方式做到这一点。

c++ templates parameters c++11 variadic-functions
10个回答
30
投票

您可以通过可变参数模板接受参数,并让typechecking在转换后检查有效性。

您可以检查功能接口级别的可转换性,以利用重载决策来拒绝完全错误的参数,例如,使用SFINAE

template<typename R, typename...> struct fst { typedef R type; };

template<typename ...Args>
typename fst<void, 
  typename enable_if<
    is_convertible<Args, ToType>::value
  >::type...
>::type 
f(Args...);

对于你的用例,如果你知道从std::array<>到你的dragon_list_t的步骤,那么你已经解决了它,虽然根据上面的第一个选项(“转换后来”):

template<typename ...Items>
dragon_list_t make_dragon_list(Items... maidens) {
    std::array<Maiden, sizeof...(Items)> arr = {{ maidens ... }};
    // here be dragons
}

如果你将它与上面的is_convertible方法结合起来,你就会有一个拒绝早期模板,它也可以对参数进行重载解析,如果不适用则拒绝它们。


0
投票

最近的提议Homogeneous variadic functions通过制作像你的第一个构造合法的东西来解决这个问题。除了使用参数包之外,您必须为其命名。此外,确切的语法似乎并不具体。

因此,根据提案,这实际上是合法的(您可以在文章中的“模板介绍人”段落中看到类似的结构):

dragon_list_t make_dragon_list(Maiden... maidens) {
    //here be dragons
}

12
投票

如果不在包中不使用template,则可变参数函数将解析为具有相同类型的所有参数。

这是一个扩展的max函数的例子,只接受ints(或可转换为int的类型)。

int maximum(int n) // last argument must be an `int`
{
    return n;
}

template<typename... Args>
int maximum(int n, Args... args) // first argument must be an int
{
    return std::max(n, maximum(args...));
}

说明:解压缩参数包(args...)时,编译器会查找最佳重载。如果包只有一个参数,那么最佳候选者是maximum(int),所以唯一的参数必须是和int类型(或可转换为int)。如果包中有多个元素,则唯一的候选者是maximum(int, typename...),因此第一个参数必须是int类型(或者可以转换为int)。通过归纳证明包中的所有类型必须是可转换为int的类型,这很简单。


11
投票

既然你已经包含了C ++ 0x标签,那么显而易见的答案就是查找initializer lists。初始化列表允许您指定ctor的多个参数,这些参数将自动转换为单个数据结构以供ctor处理。

它们的主要(独占?)用途恰好是你提到的那种情况,传递了许多相同类型的参数,用于创建某种列表/数组/其他对象集合。它将得到(例如)std::vector的支持,所以你可以使用类似的东西:

std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida};

创建一个三个dragon对象的向量。大多数(全部?)其他系列都会包含相同的内容,所以如果你真的坚持龙的列表,你也应该能够很容易地获得它。


5
投票

我最近需要将参数包限制为只有一种类型,或者至少可以转换为该类型。我最终找到了另一种方式:

#include <type_traits>
#include <string>

template <template<typename> class Trait, typename Head, typename ...Tail> 
struct check_all {
  enum { value = Trait<Head>::value && check_all<Trait, Tail...>::value };
};

template <template<typename> class Trait, typename Head>
struct check_all<Trait, Head> {
  enum { value = Trait<Head>::value };
};

template <typename ...Args> 
struct foo {
  // Using C++11 template alias as compile time std::bind
  template <typename T>
  using Requirement = std::is_convertible<double, T>;
  static_assert(check_all<Requirement, Args...>::value, "Must convert to double");
};

int main() {
  foo<int, char, float, double>();
  foo<int, std::string>(); // Errors, no conversion
}

我喜欢这个解决方案的是我可以将check_all应用于其他特性。


2
投票

虽然这个问题被标记为C ++ 11,但我认为C ++ 17 +概念解决方案值得加入,好像现在GCC中有支持,其他人很快就会关注。

首先定义一个简单的概念

class mytype{};

template<typename T>
concept bool MyType = std::is_same<T, mytype>::value;

然后只需使用可变参数模板参数

template<MyType ... Args>
void func(Args &&... args){
    // do something here
}

随着概念的出现,更容易!


1
投票

这确实取决于你想要实现的目标。

通常enum表示特定类的运行时子类型,或者是区分联合(boost :: variant)。但在这种情况下,你想直接传递enum。此外,您有一组有限的可能值,每个函数调用都构成一个子集。你真正代表的是一个子集,而不是几个参数。

表示有限集子集的最佳方法是bitset。大套应该使用std::bitset;小集可以使用unsigned long

enum Maiden_set {
    Eunice = 1,
    , Beatrice = 2
    , Una_Brow = 4
    , Helga = 8
    , Aida = 16
};

dragon_list_t make_dragon_list(Maiden_set) {
    //here be dragons
}

make_dragon_list( Eunice + Beatrice + Helga );

或者,因为您似乎想要在编译时处理变体,

template< int Maidens > // parameter is not a Maiden_set because enum+enum=int
dragon_list_t make_dragon_list() {
    //here be dragons
}

make_dragon_list< Eunice + Beatrice + Helga >(); // + promotes each enum to int

应该可以使用在operator+类型上重载的enum自动生成2的幂。但我不确定我是否走在正确的轨道上。


1
投票

我会尽量保持简单,我能想到的最简单的解决方案就是使用普通的旧矢量。通过使用C ++ 0x功能,您可以获得类似于(即使不完全)您想要的语法:

void foo( std::vector<int> const & v ) {
   std::copy( v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ") );
}
int main() {
  foo({ 1, 2, 3, 4, 5, 6 }); // note the extra {}
}

1
投票

简而言之,您应该只创建一个向量。它没有那么多开销,特别是如果你使用类似boost::list_of或C ++ 0x的初始化列表。语法开销很小,而且更灵活(你可以传递列表,其中包含许多只在运行时知道的参数)。

如果您真的想要,可以使用可变参数模板参数来执行此操作:

// Using pass-by-value since I'm assuming it is primitive:

template< typename T, typename... Args>
void make_dragon_list_internal( dragon_list_t* dragon_list, T t, Args... args )
{
   // add T to dragon_list.
   make_dragon_list_internal( dragon_list, args... );
}

void make_dragon_list_internal( dragon_list_t* dragon_list )
{
   // Finalize dragon_list.
}

template<typename... Args>
dragon_list_t make_dragon_list( Args... args )
{
  dragon_list_t dragon_list;
  make_dragon_list_internal( &dragon_list, args... );
  return dragon_list;
}

它是类型安全的,可扩展的(如果你愿意的话,你可以把它当作龙以外的东西)。


0
投票

我认为以下代码对您的案例有帮助:

template <class...>
struct IsAllSame {};

template <class T, class B1>
struct IsAllSame<T, B1> {
  static constexpr const bool kValue = std::is_same<T, B1>::value;
};

template <class T, class B1, class... Bn>
struct IsAllSame<T, B1, Bn...> {
  static constexpr const bool kValue =
      IsAllSame<T, B1>::kValue ? IsAllSame<T, Bn...>::kValue : false;
};

IsAllSame<int>::kValue == true
IsAllSame<bool, int>::kValue == false
IsAllSame<bool, int, int>::kValue == false
© www.soinside.com 2019 - 2024. All rights reserved.