我正在创建一个函数(可能是成员函数,而不是它很重要......也许它确实?)需要接受未知数量的参数,但我希望它们都是相同的类型。我知道我可以传入数组或向量,但我希望能够直接接受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})
;
但如果可能的话,我宁愿能够以第一种方式做到这一点。
您可以通过可变参数模板接受参数,并让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
方法结合起来,你就会有一个拒绝早期模板,它也可以对参数进行重载解析,如果不适用则拒绝它们。
最近的提议Homogeneous variadic functions通过制作像你的第一个构造合法的东西来解决这个问题。除了使用参数包之外,您必须为其命名。此外,确切的语法似乎并不具体。
因此,根据提案,这实际上是合法的(您可以在文章中的“模板介绍人”段落中看到类似的结构):
dragon_list_t make_dragon_list(Maiden... maidens) {
//here be dragons
}
如果不在包中不使用template
,则可变参数函数将解析为具有相同类型的所有参数。
这是一个扩展的max
函数的例子,只接受int
s(或可转换为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
的类型,这很简单。
既然你已经包含了C ++ 0x标签,那么显而易见的答案就是查找initializer lists。初始化列表允许您指定ctor的多个参数,这些参数将自动转换为单个数据结构以供ctor处理。
它们的主要(独占?)用途恰好是你提到的那种情况,传递了许多相同类型的参数,用于创建某种列表/数组/其他对象集合。它将得到(例如)std::vector
的支持,所以你可以使用类似的东西:
std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida};
创建一个三个dragon
对象的向量。大多数(全部?)其他系列都会包含相同的内容,所以如果你真的坚持龙的列表,你也应该能够很容易地获得它。
我最近需要将参数包限制为只有一种类型,或者至少可以转换为该类型。我最终找到了另一种方式:
#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
应用于其他特性。
虽然这个问题被标记为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
}
随着概念的出现,更容易!
这确实取决于你想要实现的目标。
通常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的幂。但我不确定我是否走在正确的轨道上。
我会尽量保持简单,我能想到的最简单的解决方案就是使用普通的旧矢量。通过使用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 {}
}
简而言之,您应该只创建一个向量。它没有那么多开销,特别是如果你使用类似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;
}
它是类型安全的,可扩展的(如果你愿意的话,你可以把它当作龙以外的东西)。
我认为以下代码对您的案例有帮助:
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