Interesting, I've been working on a proposal of my own for an
overloaded 'operator...' (I also have a semi-working implementation).
My approach differs from yours in a few ways, which I'll cover below.
On Mon, Nov 5, 2012 at 12:26 PM, Tomasz Kamiński <
toma...@gmail.com> wrote:
> I would like to propose addition for operator... in to set of over-loadable
> operators of C++ language.
>
> The basic syntax would be:
> template<typename T, typename U>
> struct pair
> {
> /*..*/
> T first;
> U second;
>
> operator...() { return {first, second}; } // no return type,
> // number of elements must be know on compile time.
In my proposal, the operator has a return type, which must contain an
unexpanded parameter pack. The operator... function declaration is
itself a pack expansion (expanding to a set of functions), so
unexpanded packs can be used in the function definition.
template<typename...Ts>
struct tuple : tuple_elem<...sizeof...(Ts), Ts>... {
Ts operator...() { return get<...sizeof...(Ts)>(*this); }
Ts operator...() const { return get<...sizeof...(Ts)>(*this); }
};
template<typename T, typename U>
struct pair {
T first;
U second;
decltype(...declval<tuple<T,U>>()) operator...() {
return ...tuple<T,U>(first,second);
}
};
Any reference to operator... refers to a pack of functions (the
expansions of the operator...), so (for instance)
my_tuple.operator...() contains an unexpanded parameter pack.
> This syntax allows tuples to work as augments packs, so the functions may be
> invoked as follows. t and t1, t2 are instances of different classes that
> defines operator....
> f(t...); // possible with boost::fused,
> f(1, t1.., 2, t2...); //may be implemented today as invoke(f, 1, expand(t1),
> 2, expand(t2)).
I've experimented and found this to be a bad syntactic approach, for a
few different reasons:
1) Ambiguity. In an expression like f( g(t1, t2) ... ), it's not clear
which of t1 and t2 should be expanded.
2) Support for various implementation approaches. Some approaches for
variadic templates require the compiler to know syntactically which
entities are packs and which are not, and this loses that property.
3) Support for early checking of templates. If a pack expansion
appears in a template, it becomes impossible to check whether it is
actually expanding any packs until the template is instantiated.
In essence, in C++11 the "contains an unexpanded parameter pack"
predicate is a syntactic one, and this approach breaks that property.
Instead, I add a unary prefix operator '...', which is used to
transform an expression into a pack (note that prefix is the
appropriate choice here because the behavior of the ellipsis in
(almost) all existing contexts depends solely on the token(s) which
follow it).
Hence a tuple can be expanded to arguments to a single function:
f(...t1...) -> f(t1a, t1b, t1c)
... or can be converted into a pack for later use:
f( g(...t1) ...) -> f(g(t1a), g(t1b), g(t1c))
... or whatever else you want to do.
f( g(...t1, ...t2...) ...) -> f(g(t1a, t2a, t2b, t2c), g(t1b, t2a,
t2b, t2c), g(t1c, t2a, t2b, t2c))
My implementation allows a prefix ellipsis to be applied to an integer
constant expression, producing a pack {0, 1, ... N-1}. You can see
this in use in the above tuple example.
> The implementation for std::array may use following library:
> template<std::size_t B, std::size_t E>
> struct constant_range
> {
> constexpr operator...() const { return { B, constant_range<B+1,E>()...};
> } //note the use of constexpr
> };
>
> template<std::size_t E>
> struct constant_range
> {
> consexpr operator...() const { return {} };
> };
This is a slow implementation (its compilation time grows
quadratically in the number of elements produced), and is one of the
primary motivators for supporting the ...N syntax: C++11 does not
admit a method of constructing a pack of N elements (for some unknown
compile-time constant N) in linear compile time.
My implementation lives here:
https://github.com/zygoloid/clang/tree/pack-expressions