问题
Assume that there is a function which accepts several strings:
void fun (const std::initializer_list<std::string>& strings) {
for(auto s : strings)
// do something
}
Now, I have a variadic template
function say foo()
as:
template<typename ...Args>
void foo () {
fun(???);
}
This method is called externally as:
foo<A, B, C, D>(); // where A, B, C, D are classes
And these classes which are passed as arguments are expected to contain a common static const
member:
static const std::string value = "...";
Here are my questions (how to):
- When inside
foo()
, check if all theArgs
containvalue
usingstatic_assert
- Pass all such values to
fun()
to form aninitializer_list
; e.g.fun({A::value, B::value, ...});
Searched several threads related to variadic templates and its unpacking but I am still novice in this area. Explanation in little more detail is much appreciated.
回答1:
As for the second question, just do it this way:
template<typename ...Args>
void foo () {
fun({Args::value...});
}
The mechanism is pretty intuitive: you create an initalizer list that contains the expanded Args::value
pattern, thus resolving (in your case) to { A::value, B::value, C::value, D::value }
.
Here is a complete program:
#include <string>
#include <iostream>
void fun (const std::initializer_list<std::string>& strings) {
for(auto s : strings)
{
std::cout << s << " ";
}
}
template<typename ...Args>
void foo () {
fun({Args::value...});
}
struct A { static std::string value; };
struct B { static std::string value; };
struct C { static std::string value; };
struct D { static std::string value; };
std::string A::value = "Hello";
std::string B::value = "World";
std::string C::value = "of";
std::string D::value = "Variadic Templates";
int main()
{
foo<A, B, C, D>(); // where A, B, C, D are classes
}
And here is a live example.
As for the static assertion, you may write a type trait that determines whether a certain type has a member variable value
:
template<typename T, typename V = bool>
struct has_value : std::false_type { };
template<typename T>
struct has_value<T,
typename std::enable_if<
!std::is_same<decltype(std::declval<T>().value), void>::value,
bool
>::type
> : std::true_type
{
typedef decltype(std::declval<T>().value) type;
};
Then, you could use it this way:
template<typename T>
struct check_has_value
{
static_assert(has_value<T>::value, "!");
};
template<typename ...Args>
void foo () {
auto l = { (check_has_value<Args>(), 0)... };
fun({Args::value...});
}
Here is a live example of a successful check (all classes has a value
data member). Here is a live example of an unsuccessful check (class D
's data member is called values
)
回答2:
The second part is easier:
template<typename ...Args>
void foo () {
fun({Args::value...});
}
The first part is tricky, because static_assert
is a declaration, not an expression, so you'd have to expand the variadic pack within the first parameter. It may be easier just to let the call to fun
do the checking for you. Here's a sketch of how to do it with an auxiliary all
constexpr
function:
constexpr bool all() { return true; }
template<typename... Args> constexpr bool all(bool first, Args&&... rest) {
return first && all(rest...);
}
template<typename ...Args>
void foo () {
static_assert(all(std::is_convertible<decltype(Args::value),
std::string>::value...), "All Args must have a value");
fun({Args::value...});
}
回答3:
Here's an answer to both points:
#include <initializer_list>
#include <iostream>
#include <string>
#include <type_traits>
using namespace std;
void fun (const std::initializer_list<std::string>& strings) {
for(auto s : strings)
cout << s << endl;
}
// This uses SFINAE to find if there's a string T::value in T
template <typename T>
struct HasValue
{
typedef char OK; //sizeof() guaranteed 1
struct BAD { char x[2]; }; //sizeof() guaranteed >1
template <const string *>
struct Helper;
template <typename X>
static OK has(X*, Helper<&X::value>* = nullptr); //SF if &X::value is not a const string*
static BAD has(...); //will be picked in SF case
static const bool value = (sizeof(has((T*)nullptr)) == sizeof(OK));
};
// This template (and its specialisation) ensure all args have ::value
template <typename H, typename... T>
struct HaveValue : public integral_constant<bool, HasValue<H>::value && HaveValue<T...>::value>
{};
template <typename H>
struct HaveValue<H> : public HasValue<H>
{};
template <typename... Args>
void foo() {
static_assert(HaveValue<Args...>::value, "All arguments must have const string ::value");
fun({Args::value...}); //answer to point 2: create the initialiser list
}
// Example data follow
struct A
{
static const string value;
};
const string A::value = "AA";
struct B
{
static const string value;
};
const string B::value = "BB";
struct C{};
int main()
{
foo<A, B>();
//foo<A, B, C>(); //uncomment to have the static assertion fire
}
See it live.
来源:https://stackoverflow.com/questions/16478089/converting-variadic-template-pack-into-stdinitializer-list