问题
I'm trying to do something similar to C++11 variable number of arguments, same specific type, but I have my own type:
struct Foo
{
Foo(int) {}
Foo(int, int) {}
};
with a bunch of overloads
void f() {}
void f(const Foo&) {}
void f(const Foo&, const Foo&) {}
// etc. ... as many f() overloads as needed ...
works as desired: f(); f(1); f(1, 2); f(1, { 2, 3 });
.
Instead of overloads, I can also use std::initializer_list<>
with the {}
syntax (as suggested here):
void g_(std::initializer_list<Foo>) {}
g_({}); g_({ 1 }); g_({ 1, 2 }); g_({ 1, { 2, 3 } });
but that has an extra set of {}
(yeah, it's just two characters). To match the syntax of f()
exactly, use a macro:
#define g(...) g_({__VA_ARGS__})
g(); g(1); g(1, 2); g(1,{ 2, 3 });
(Matching the syntax of f()
exactly might be needed because of legacy or generated code. ... and it—arguably—just "looks better".)
But I can't find a way to make a variadic template work
void h() { }
template<typename... T>
void h(const Foo&, T...) { }
h()
, h(1)
and h(1, 2)
work, but h(1, {2, 3})
fails to compile because the compiler can't figure out the type of {2, 3}
as it can with f()
and g_()
.
Is there a way to make f()
work without multiple overloads? Or for g()
to work without a macro? g()
is very close (only one function and no template magic), but there's that macro ...
回答1:
{}
requires you are initializing something of a specific type.
C++11 variable arguments requires that your types be deducted types.
These are opposing requirements.
Now I could generate an object with a set of ()
overloads up to some large finite number.
namespace details {
template<std::size_t, class T>
using ignore_index=T;
template<class T, class Count, class Base>
struct linear_overload_count;
template<class T, std::size_t I0, std::size_t...Is, class Base>
struct linear_overload_count<T, std::index_sequence<I0,Is...>, Base>:
linear_overload_count<T, std::index_sequence<Is...>, Base>
{
using linear_overload_count<T, std::index_sequence<Is...>, Base>::operator();
using linear_overload_count<T, std::index_sequence<Is...>, Base>::linear_overload_count;
std::result_of_t<
Base const&(T const&, ignore_index<Is,T>const&...)
>
operator()(T const& t0, ignore_index<Is,T>const&...ts) const {
return Base::operator()(t0, ts...);
}
linear_overload_count()=default;
linear_overload_count(linear_overload_count const&)=default;
linear_overload_count(linear_overload_count &&)=default;
linear_overload_count& operator=(linear_overload_count const&)=default;
linear_overload_count& operator=(linear_overload_count &&)=default;
};
template<class T, class Base>
struct linear_overload_count<T, std::index_sequence<>, Base>:
Base
{
using Base::Base;
linear_overload_count(Base&& b):Base(std::move(b)) {}
linear_overload_count(Base const& b):Base(b) {}
std::result_of_t<
Base const&()
>
operator()() const {
return Base::operator()();
}
linear_overload_count()=default;
linear_overload_count(linear_overload_count const&)=default;
linear_overload_count(linear_overload_count &&)=default;
linear_overload_count& operator=(linear_overload_count const&)=default;
linear_overload_count& operator=(linear_overload_count &&)=default;
};
}
template<class T, std::size_t N, class Base>
using linear_overload_Ts = details::linear_overload_count<T, std::make_index_sequence<N>, Base>;
auto count_args_impl = [](auto&&...args) { std::cout << sizeof...(args) << "\n"; };
struct bob {
int x,y;
};
using count_bobs_t = linear_overload_Ts< bob, 3, decltype(count_args_impl) >;
count_bobs_t const bobs = count_args_impl;
int main() {
bobs();
bobs({}, {}, {1,2});
}
live example.
Now we can have up to 100 overloads in bobs
by changing the number 3
above to 100.
Note that if you hit a more than a few 100, your compiler will die. This can be fixed with a binary tree inheritance instead of a linear one, but I cannot be bothered.
In addition, this technique can slow down compiling.
Note that Base
must be a type. You can use a lambda like above to forward to your template function (give them different names), a manual function object, or whatever else.
Using this technique to create a function instead of a function object isn't something I can solve without the help of a type being named in the call (so, use ADL to find the generated function). Function objects don't participate in overload resolution in the same way that functions do, which may be a problem.
This also seems like a lot of work to do away with adding an extra set of {}
.
回答2:
If you want a bunch of Foo
s, and you want to allow braced-init-lists, then you should do:
void foo(std::initializer_list<Foo> );
Yes, this requires one extra set of braces. No, you shouldn't use a macro to be able to omit those two characters.
You can't use a variadic template here because a braced-init-list is not an expression and it does not have a type, so it can't be deduced.
来源:https://stackoverflow.com/questions/41831270/variable-number-of-arguments-same-specific-type-without-macro-or-initializer-li