问题
I\'m creating a function (possibly member function, not that it matters... maybe it does?) that needs to accept an unknown number of arguments, but I want all of them to be the same type. I know I could pass in an array or vector, but I want to be able to accept the list of args directly without extra structure or even extra brackets. It doesn\'t look like variadic functions by themselves are typesafe, and I wasn\'t sure how to go about this w/ variadic template functions. Here\'s essentially what I\'m aiming for (more than likely not correct code, and totally not for the purpose of getting lists of dragons, lol):
//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
}
OR
template<Maiden... Maidens> dragon_list_t make_dragon_list(Maidens...) {
//here be dragons
}
USAGE
dragon_list_t dragons_to_slay
= make_dragon_list(Maiden.Eunice, Maiden.Helga, Maiden.Aida)
;
Tried a few things similar to the above already, no dice. Suggestions? Obvious oversights I may have made? I know it may not be a huge deal to do this instead:
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})
;
but I\'d much rather be able to do it the first way if possible.
回答1:
You can just accept the arguments by the variadic template and let typechecking check the validity later on when they are converted.
You can check convertibility on the function interface level though, to make use of overload resolution for rejecting outright wrong arguments for example, by using 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...);
For your use-case if you know the steps to go from an std::array<>
to your dragon_list_t
then you have already solved it though according to the first option above ("convert-later"):
template<typename ...Items>
dragon_list_t make_dragon_list(Items... maidens) {
std::array<Maiden, sizeof...(Items)> arr = {{ maidens ... }};
// here be dragons
}
If you combine this with the above is_convertible
approach you have a reject-early template that also does overload resolution on arguments and rejects them if not applicable.
回答2:
If you don't use template
on the parameter not in the pack the variadic function will resolve to have all arguments of the same type.
Here's an example for an extended max
function that only accepts int
s (or types convertible to 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...));
}
Explanation: When you unpack the argument pack (args...
) the compiler looks for the best overload. If the pack had only one parameter then the best candidate is maximum(int)
so the only parameter must be and of type int
(or convertible to int
). If there are more than one elements in the pack then the only candidate is maximum(int, typename...)
so the first argument must be of type int
(or convertible to int
). It's simple to prove by induction that all the types in the pack must be of a type convertible to int
).
回答3:
Since you've included the C++0x tag, the obvious answer would be to look up initializer lists. An initializer list lets you specify a number of arguments to a ctor that will be automatically converted to a single data structure for processing by the ctor.
Their primary (exclusive?) use is for exactly the sort of situation you've mentioned, passing a number of arguments of the same type to use in creating some sort of list/array/other collection of objects. It'll be supported by (for one example) std::vector
, so you could use something like:
std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida};
to create a vector of three dragon
objects. Most (all?) of the other collections will include the same, so if you really insist on a list of dragons you should be able to get that pretty easily as well.
回答4:
I recently needed to constrain a parameter pack to be only one type, or at least convertible to that type. I ended up finding another way:
#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
}
The thing that I liked about this solution is that I can apply check_all
to other traits too.
回答5:
A recent proposal, Homogeneous variadic functions, addresses this by making something like your first construct legal. Except of course to use the parameter pack you will have to name it. Also the exact syntax doesn't seem very concrete yet.
So, under the proposal this will actually be legal (you can see a similar construct in the paragraph "The template introducer" in the paper):
dragon_list_t make_dragon_list(Maiden... maidens) {
//here be dragons
}
回答6:
Although the question is tagged C++11, I think a C++17 + concepts solution would be worth adding seeing as though there is now support in GCC and others will soon follow.
first define a simple concept
class mytype{};
template<typename T>
concept bool MyType = std::is_same<T, mytype>::value;
then simply use variadic template parameters
template<MyType ... Args>
void func(Args &&... args){
// do something here
}
Much easier with the advent of concepts!
回答7:
It really depends what you're trying to implement, exactly.
Usually enum
indicates runtime subtypes of a particular class, or a discriminated union (boost::variant). But in this case, you want to pass the enum
directly. Moreover, you have a limited set of possible values, and each function call forms a subset. Really what you are representing is one subset, not several parameters at all.
The best way to represent a subset of a finite set is a bitset. Large sets should use std::bitset
; small sets can just use 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 );
or, since you appear to want to handle variation at compile time,
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
It should be possible to generate the powers of 2 automatically using an operator+
overloaded on the enum
type. But I'm not sure I'm on the right track at all.
回答8:
I would try to keep things simple, and the simplest solution I can think of is just using a plain old vector. By using C++0x features you can get a syntax that is similar to (even if not exactly) what you want:
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 {}
}
回答9:
In short, you should probably just create a vector. It isn't that much overhead, especially if you use something like boost::list_of or C++0x's initializer list. The syntactic overhead is minimal, and it is more flexible (you could pass list with a number of arguments known only at runtime).
If you really wanted, you could use variadic template parameters to do this:
// 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;
}
It's typesafe, and extensible (you could make this take things other than dragons, if you feel like).
回答10:
I think the following code is helpful for your case:
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
来源:https://stackoverflow.com/questions/3703658/specifying-one-type-for-all-arguments-passed-to-variadic-function-or-variadic-te