Reputation: 5409
The main problem in question is that I want to write a function that takes two variable-length sets of arguments.
The abstraction I decided to go for is to emulate the following call syntax:
f({a,b,c},x,y);
If a
, b
, and c
all have the same type, this can be made to work with
template <typename X, typename... A>
void f(std::initializer_list<X> xs, A&&... as) { . . . }
But a similar function definition with std::tuple
instead of the std::initializer_list
does not allow for my desired syntax.
template <typename... X, typename... A>
void f(const std::tuple<X...>& xs, A&&... as) { . . . }
Is there some trick I can use to allow the types in the first group to be heterogeneous?
Addendum: In general, I do not apriori know the size of X...
.
Addendum 2: Does anyone know the technical reason why a braced initializer works for std::tuple t{a,b,c}
, but not in the template argument context? Is it because in my example xs
is not expanded by X...
?
Upvotes: 1
Views: 658
Reputation: 14603
What you want is currently inconvenient, but there's another alternative to the one already provided:
f(a, b, c)(x)(y)();
If you exploit the fact, that f
does not return anything, you can return a lambda or some other function object that accepts further arguments. An empty argument list could mean, that there will be no further arguments.
Example:
template <typename ...A>
auto f(A&& ...a)
{
if constexpr (sizeof...(a) > 1)
{
std::tuple(std::forward<A>(a)...);
}
else
{
// burp
}
return [](auto&& ...a)
{
if constexpr (sizeof...(a))
{
return f(std::forward<decltype(a)>(a)...);
}
else
{
std::cout << "done" << std::endl;
}
};
}
int main()
{
f(1, 2, 3)(4)(5)();
return 0;
}
https://wandbox.org/permlink/eAB2S4aCxxiSlMAF
EDIT: Try also this alternative:
template <typename ...A>
void f(std::any const(&)[3], A&&...)
{
}
int main()
{
f({1, 2u, '3'}, 4., 5.f);
return 0;
}
https://wandbox.org/permlink/0OhoJpQ1YpSGOV2c
You can use a std::variant
for type safety or something faster than what the STL provides. You can also write your own container, of course.
EDIT2: Then again:
template <typename ...A>
void f(std::initializer_list<std::any>, A&&...)
{
}
int main()
{
f({1, 2u, '3'}, 4., 5.f);
return 0;
}
Don't use std::any
, of course, but your own fancy type.
https://wandbox.org/permlink/rqSVK6aj5zuSFRcf
EDIT3: There is yet another way:
class U
{
void const* const v_;
using typeid_t = void(*)();
typeid_t const tid_;
public:
template <typename T>
static typeid_t type_id() noexcept
{
return typeid_t(U::type_id<T>);
}
template <typename A>
U(A&& a) noexcept: v_(&a), tid_(type_id<std::remove_cvref_t<A>>()) {}
template <typename A>
A const& get() const noexcept { return *static_cast<A const*>(v_); }
auto type_id() const noexcept { return tid_; }
};
template <typename... A>
void f(U const (&a)[3], A&&...)
{
std::cout << a[0].get<double>() << std::endl;
}
int main()
{
f({1.5, 2, 3.f}, 'a', 1);
return 0;
}
You can make typeid
checking optional and compile it only in the debug build.
https://wandbox.org/permlink/wgXOycB5I3bb3ExV
Upvotes: 1
Reputation: 22152
No, it is currently impossible with the syntax you want. The initializer list can only be deduced to an array or a std::initializer_list
, both of homogeneous types.
This is a special rule. An initializer list has no type and simply cannot be deduced to anything else. If the function parameter is not an array or a std::initializer_list
, then the parameter for which the initializer list was given becomes a non-deduced context and template argument deduction will ignore it.
In particular class template argument deduction cannot be done in a function parameter to determine the type from a braced initializer list as it can be done in a variable definition since C++17. Such a feature was not added to the language.
In principle you could allow a finite number of different types by using e.g. an array of std::variant
as parameter, but that would be resolved to the type only at runtime and is unlikely to be what you want.
You need to either add a type name or function call to the initializer list braces as in
f(std::forward_as_tuple(a, b, c), x, y)
or you can use a tag type instead to indicate the end of the first pack in a flat argument list:
f(a, b, c, tag, x, y)
The tag can then be detected with some template work.
Upvotes: 1