template<typename... L> void foo() {
for (typename T: L...) {
// do the stuff
}
}
template<typename... A> void foo(A&& a) {
for (const auto& arg: a...) {
// do the stuff
}
}
template<size_t... I> void foo() {
for (auto i: I...) {/*the code goes here*/}
// as well as
for (size_t i: I...) {/*the code goes here*/}
}
template<size_t... I> void foo() {
for (size_t i: {I...}) {/*the code goes here*/}
}
template<typename... V>
class variant_ptr {
public:
template<typename Func, typename... A>
auto invoke(Func&& f, A&&... a) const {
size_t idx = 0;
for (typename T: V...) {
if (type_idx_ == idx++)
return std::invoke(std::forward<Func>(f), reinterpret_cast<T*>(data_), std::forward<A>(a)...);
}
assert(false);
throw std::logick_error("should never happen");
}
private:
void* data_;
size_t type_idx_;
};
Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options. After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((f(std::forward<Args>(args)), 0)...);
}
среда, 8 июня 2016 г., 21:11:21 UTC+6 пользователь Greg Marr написал:On Wednesday, June 8, 2016 at 10:14:50 AM UTC-4, Sergey Vidyuk wrote:Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options. After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:Have you seen Sean Parent's for_each_argument?template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((f(std::forward<Args>(args)), 0)...);
}
template<typename Func, typename... A>
auto invoke(Func&& f, A&&... a) const {
switch constexpr (type_idx_) {
case... indexof(V): return std::invoke(std::forward<Func>(f), static_cast<V*>(data_), std::forward<A>(a)...);
}
throw std::logic_error("should never happen");
}
Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options.
After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:template<typename... L> void foo() {
for (typename T: L...) {
// do the stuff
}
}template<typename... A> void foo(A&& a) {
for (const auto& arg: a...) {
// do the stuff
}
}
In both cases the loop is unrolled in compile time and it's body is instantiate for each exact type in the same way as a body of template function. Each instantiation of a body has it's own scope and there are no outter scope polution posibility. Standard loop operators break and continue should also be allowed for both cases and work in the same way as in any runtime C++ loop.
On Wednesday, June 8, 2016 at 7:14:50 AM UTC-7, Sergey Vidyuk wrote:Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options.FWIW, I don't think that today's option, "abuse of comma operator folding", is very bad. It would be nice to have a fold-expression-esque (fold-statement?) syntax for folding over semicolons; but I have no concrete idea of what that would look like.
After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:template<typename... L> void foo() {
for (typename T: L...) {
// do the stuff
}
}template<typename... A> void foo(A&& a) {
for (const auto& arg: a...) {
// do the stuff
}
}
In both cases the loop is unrolled in compile time and it's body is instantiate for each exact type in the same way as a body of template function. Each instantiation of a body has it's own scope and there are no outter scope polution posibility. Standard loop operators break and continue should also be allowed for both cases and work in the same way as in any runtime C++ loop.My kneejerk reaction was that for (typename T /*...*/) is a non-starter, because you're proposing to have it mean something very different from for (int I /*...*/).Plus, you'll have troublesome grammar issues if the user tries to use the new syntax to replace a repeated statement where the repeated statement contains a "break" or "continue".However, on further reflection, I think that I might just be approaching the problem from the wrong angle — I've been thinking of it as "a way to textually replace sizeof...(P) repeats of the same statement with a single statement" (i.e. a variant of folding), whereas I think you're thinking of it as "a way to control-flow over compile-time entities" (i.e. a variant of if-constexpr). I actually think your syntax is reasonable with a very small tweak, and here's why:There is currently a very well-received-in-Jacksonville proposal (P0292R1) to add "if constexpr" to the language.If integer indexing into packs were proposed and accepted, then the obvious syntax for looping over a pack at compile-time would befor constexpr (size_t i=0; i < sizeof...(Ts); ++i) { // new syntax ("for constexpr")using T = Ts...[i]; // new syntax (pack indexing)f<T>();}or (arguably; anyway I'm not convinced that it's a horrible idea) that could be reduced to justfor constexpr (typename T : Ts...) { // new syntax ("for constexpr")f<T>();}The remaining problem is that I'm not sure the "obvious" syntax (above) actually makes any sense. We want to be able to ++i, but at the same time use f<i>(). Perhaps i should magically be constexpr in the body of the loop, but non-constexpr in the header of the loop?I've emailed Jens Maurer separately to ask what the semantics of if constexpr are supposed to be in similar cases, and will report back on the results if he doesn't post here first. :)–Arthur
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/f11d3266-3407-4c00-a578-5d07beeb1071%40isocpp.org.
On Thu, Jun 9, 2016 at 1:13 PM, Richard Smith <ric...@metafoo.co.uk> wrote:
> On Thu, Jun 9, 2016 at 12:06 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
>>
>> FWIW, I don't think that today's option, "abuse of comma operator
>> folding", is very bad. It would be nice to have a fold-expression-esque
>> (fold-statement?) syntax for folding over semicolons; but I have no concrete
>> idea of what that would look like.
>
> I think last time this came up the reasonable options were:
>
> expr_stmt() ... ; // #1: problem: what does "if (blah) expr_stmt() ...;"
> mean?
> expr_stmt(), ... ; // #2: make outermost parens in comma fold optional in
> this context
> ( expr_stmt(), ... ) ; // #3: status quo, a little verboseI don't care about any of the above options, because they're all isomorphic to #3, which we have today (well, in C++17 we will). When I say "fold-statement" and "folding over semicolons", I mean folding that expands to things that aren't expr-stmts.template<typename... Ts>void f() {( if (is_same_v<int, Ts>) return; )...;puts("None of my parameters are int.");}Admittedly that's a toy example (I could just use if (any_of(...)) instead), and I don't have any non-toy examples of my own. I think Sergey's "variant_ptr::invoke" example is a good example of the problem, in that I'm having a lot of trouble refactoring it into anything that works equally well in C++17.
template<typename Func, typename... A>
auto invoke(Func&& f, A&&... a) const {
return switch_(std::make_index_sequence<sizeof...(V)>{},
type_idx_,
[&] { return std::invoke(std::forward<Func>(f), static_cast<V*>(data_), std::forward<A>(a)...); }...,
[] { throw std::logic_error("should never happen"); }
);
}
template<class T, T... I, class... F, class D>
auto switch_(std::integer_sequence<T, I...>, T i, F&&... f, D&& d) {
switch (i) {
case... I : return std::forward<F>(f)();
}
return std::forward<D>(d)();
}> The rules we originally picked for fold-expressions were deliberately very
> conservative; relaxing them is probably a good idea.
Agreed FWIW, but I think not useful for Sergey's use-case.
On Wednesday, 8 June 2016 19:03:33 UTC+1, Sergey Vidyuk wrote:среда, 8 июня 2016 г., 21:11:21 UTC+6 пользователь Greg Marr написал:On Wednesday, June 8, 2016 at 10:14:50 AM UTC-4, Sergey Vidyuk wrote:Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options. After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:Have you seen Sean Parent's for_each_argument?template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((f(std::forward<Args>(args)), 0)...);
}Well, sure it's possible to emulate variadic for using existing language/library features, but a new language feature (built-in control flow construct) is more readable and integrates better; most of the same arguments for if constexpr (p0128r1) hold here as well.I'm not sure about the motivating example, though; it seems to me that in that case what you really want is a switch statement:template<typename Func, typename... A>
auto invoke(Func&& f, A&&... a) const {
switch constexpr (type_idx_) {
case... indexof(V): return std::invoke(std::forward<Func>(f), static_cast<V*>(data_), std::forward<A>(a)...);
}
throw std::logic_error("should never happen");
}Do you have any other examples with stronger motivation e.g. where each iteration has a side effect, or where you conditionally break/return depending on something more complex than a switch-style predicate?
template<typename T, typename... PropertyInfo>
void set(T& obj, const std::string& property, const Json::Value& val) {
bool property_found = false;
for constexpr (typename PI: PropertyInfo...) {
static_assert(std::is_same_v<PI::class_type, T>);
if (PI::name != property)
continue;
property_found = true;
PI::set(obj, parse<PI::property_type>(val));
break;
}
if (!property_found)
throw std::runtime_error("property not found");
}Aside from that, the syntax doesn't look to me to be distinctive enough; just having the for-range-initializer end in an ellipsis could be confused with e.g. a fold. (Yes, folds are parenthesized, but it's easy to overlook that.) Bikeshedding a bit, but I'd follow the "if constexpr" path and spell it "for constexpr". Perhaps we could end up with a full family of constexpr control flow statements.
for constexpr (typename T: L...) statement;
for constexpr (auto arg: args...) statement;On Wednesday, June 8, 2016 at 7:14:50 AM UTC-7, Sergey Vidyuk wrote:Expression folding on a variadic template parameters pack are great and allows to write internal function stuff inside a function instead of writing some recursive template code which is required to be moved outside of the function. But when one want to repeat some statement for every element of a parameter pack recursive templates or abusing of coma operator folding are stil the only options.FWIW, I don't think that today's option, "abuse of comma operator folding", is very bad. It would be nice to have a fold-expression-esque (fold-statement?) syntax for folding over semicolons; but I have no concrete idea of what that would look like.
After facing this issue several times I start to realize that it would be great to have the following extension to the C++11 range-for syntax:template<typename... L> void foo() {
for (typename T: L...) {
// do the stuff
}
}template<typename... A> void foo(A&& a) {
for (const auto& arg: a...) {
// do the stuff
}
}
In both cases the loop is unrolled in compile time and it's body is instantiate for each exact type in the same way as a body of template function. Each instantiation of a body has it's own scope and there are no outter scope polution posibility. Standard loop operators break and continue should also be allowed for both cases and work in the same way as in any runtime C++ loop.My kneejerk reaction was that for (typename T /*...*/) is a non-starter, because you're proposing to have it mean something very different from for (int I /*...*/).Plus, you'll have troublesome grammar issues if the user tries to use the new syntax to replace a repeated statement where the repeated statement contains a "break" or "continue".
However, on further reflection, I think that I might just be approaching the problem from the wrong angle — I've been thinking of it as "a way to textually replace sizeof...(P) repeats of the same statement with a single statement" (i.e. a variant of folding), whereas I think you're thinking of it as "a way to control-flow over compile-time entities" (i.e. a variant of if-constexpr). I actually think your syntax is reasonable with a very small tweak, and here's why:There is currently a very well-received-in-Jacksonville proposal (P0292R1) to add "if constexpr" to the language.If integer indexing into packs were proposed and accepted, then the obvious syntax for looping over a pack at compile-time would befor constexpr (size_t i=0; i < sizeof...(Ts); ++i) { // new syntax ("for constexpr")using T = Ts...[i]; // new syntax (pack indexing)f<T>();}or (arguably; anyway I'm not convinced that it's a horrible idea) that could be reduced to justfor constexpr (typename T : Ts...) { // new syntax ("for constexpr")f<T>();}The remaining problem is that I'm not sure the "obvious" syntax (above) actually makes any sense. We want to be able to ++i, but at the same time use f<i>(). Perhaps i should magically be constexpr in the body of the loop, but non-constexpr in the header of the loop?I've emailed Jens Maurer separately to ask what the semantics of if constexpr are supposed to be in similar cases, and will report back on the results if he doesn't post here first. :)–Arthur
for constexpr (size_t i=0; i < sizeof...(Ts); ++i) { // new syntax ("for constexpr")using T = Ts...[i]; // new syntax (pack indexing)f<T>();}
using type = std::tuple<int, long long, char>;
int main()
{
for constexpr (typename T : type) //without `...`
{
f<T>();
} //equal: `f<int>(); f<long long>(); f<char>();`
}template<typename T>
using MetaFunc = std::tuple<int, T, T, char>;
int main()
{
for constexpr (typename T : MetaFunc<long>)
{
f<T>();
} //equal: `f<int>(); f<long>(); f<long>(); f<char>();`
for constexpr (typename T : MetaFunc<char>)
{
for constexpr (typename TT : MetaFunc<T>)
{
g<T,TT>();
}
}
}
template<typename... Types>
void select(int i)
{
switch (i)
{
for constexpr (typename T : Types...)
{
case T::id: T::func();
break; //break for `for constexpr`
}
break; //break for `switch`
default:
f();
break;
}
}I'm not sure that it is; it more motivates a switch than a for statement. The C++17 equivalent would look like this:template<typename Func, typename... A>
auto invoke(Func&& f, A&&... a) const {
return switch_(std::make_index_sequence<sizeof...(V)>{},
type_idx_,
[&] { return std::invoke(std::forward<Func>(f), static_cast<V*>(data_), std::forward<A>(a)...); }...,
[] { throw std::logic_error("should never happen"); }
);
}Here switch_ emulates a switch statement with variadic expansion of case labels:template<class T, T... I, class... F, class D>
auto switch_(std::integer_sequence<T, I...>, T i, F&&... f, D&& d) {
switch (i) {
case... I : return std::forward<F>(f)();
}
return std::forward<D>(d)();
}switch_ is tricky but not impossible to implement today; the choices are either to use recursion (with penalty on usability and performance) or to use the preprocessor (via Boost.Preprocessor) with a configurable upper limit on number of case statements.> The rules we originally picked for fold-expressions were deliberately very
> conservative; relaxing them is probably a good idea.
Agreed FWIW, but I think not useful for Sergey's use-case.If we had variadic expansion of case labels that would solve that particular use case, and very elegantly.
With `for constexpr` we should have `switch` for free:
template<typename... Types>
void select(int i)
{
switch (i)
{
for constexpr (typename T : Types...)
{
case T::id: T::func();
break; //break for `for constexpr`
}
break; //break for `switch`
default:
f();
break;
}
}
Bikeshed: I don't think "..." is required after "Types"; you don't write "for (x : vs...)"Usages:template <typename... Types>int f(){int count = 0;for constexpr (typename T : Types){if(is_trivially_constructible<T>::value) count++;}return count;}
template<class... Ts>
constexpr int f() {
return (0 + ... + (is_trivially_constructible<Ts>::value ? 1 : 0));
}
// Reaching far into the future, when constexpr is even more magic,// we might see code like this:template <int... xs>constexpr int sum(){int total = 0;for constexpr(int x : xs)total += x;return total;}which seems much more readable than the current way you calculate this function (and much more like 'idiomatic' C/C++)
template<int... Is>
constexpr int sum() {
return (0 + ... + Is);
}void f() //normal function, not need to be template or be in template class
{
for constexpr (typename T : std::tuple<int, long, int>)
{
g<T>();
} //equal: `g<int>(); g<long>(); g<int>();`
}--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/2hdpLnXJKkQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/bf02cde4-5da6-4a0b-903f-8b9230d4869c%40isocpp.org.
On 11 Jun 2016 10:33 a.m., "Ryan Ingram" <ryani...@gmail.com> wrote:
>
> > This can be done already today with a fold
>
> I used intentionally toy examples, everyone knows you demo for loops with something simple like "sum" before you move on to real examples.
>
> I really like this proposal; consider this slight modification:
>
> int total = 0;
> for constexpr(int x : xs)
> {
> if(x == 0) break;
> if(x & 1) total += x;
> }
> return total;
>
> It's a lot harder to implement with a c++1z fold. You can almost certainly do it with a custom type and operator overloading, or perhaps by abusing the comma and ternary operators, but it's a lot uglier and I bet obscures the meaning.
Agreed, folds only work well for simple cases. But you don't need for constexpr to write your algorithm; you can use normal iteration over an initializer list:
int total = 0;
for (int x : {xs...})
{
if(x == 0) break;
if(x & 1) total += x;
}
return total;
(And the ranges guys might have their own opinions on how better to write this.)
I'm in favor of this proposal! But I need to see motivating examples that are actually difficult to write today; it would wreck the proposal to be presented along with examples that can easily be dismissed as already easy to code.
To my mind, that means that examples need to actually exploit the distinctive feature of for constexpr, which is that types (and templates and constants) can be different from one iteration of the loop to the next, while (preferably) also taking advantage of the fine-grained flow control we get from the loop control statements. Otherwise, you could just use boost::mpl::for_each, or (ugh) a comma fold.
x.cc:1:15: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
constexpr int f() { int i = 42; while (true) ++i; return i; }
^
x.cc:1:46: note: constexpr evaluation hit maximum step limit; possible infinite loop?
constexpr int f() { int i = 42; while (true) ++i; return i; }
^
The (minor) advantage of the general-purpose for-loop syntax as opposed to the ranged-over-pack syntax is that for any given loop-of-N-iterations, the latter requires the compiler to construct an actual entity, a pack-of-N-elements; whereas the former doesn't. They both take O(N) time, but only the latter takes O(N) space as well.I'm in favor of this proposal! But I need to see motivating examples that are actually difficult to write today; it would wreck the proposal to be presented along with examples that can easily be dismissed as already easy to code.
To my mind, that means that examples need to actually exploit the distinctive feature of for constexpr, which is that types (and templates and constants) can be different from one iteration of the loop to the next, while (preferably) also taking advantage of the fine-grained flow control we get from the loop control statements. Otherwise, you could just use boost::mpl::for_each, or (ugh) a comma fold.
template<typename... Op>
int CodeInterpretator(Uint8* code)
{
int ret = 0;
OpCodeState state;
beginLoop: while (true)
{
switch (*code++)
{
for constexpr (typename T : Op...)
{
case T::OpCodeId:
ret = T::OpCodeFunc(&state);
if (ret)
goto specialCases;
else
break;
}
break;
default:
Unreachable();
}
}
specialCases:
if (ret == R_RETURN)
return state.RetValue;
else if (ret == R_JUMP)
code = *(Uint8**)code;
else if (ret == R_ERROR)
throw OpException("Something go wrong", state);
goto beginLoop;
}switch (x)
{
for constexpr (typename T : Op...)
{
case T::OpId: T::OpFunc(); break;
}
break;
}
//After `for constexpr` "unroll"
switch (x)
{
case Op0::OpId: Op0::OpFunc(); break;
case Op1::OpId: Op1::OpFunc(); break;
case Op2::OpId: Op2::OpFunc(); break;
case Op3::OpId: Op3::OpFunc(); break;
}
| From: inkwizyt...@gmail.com Sent: Sunday, June 12, 2016 7:13 AM To: ISO C++ Standard - Future Proposals Reply To: std-pr...@isocpp.org Subject: Re: [std-proposals] Re: statement folding on variadic templates parameters pack |
How do I break from the for constexpr, if 'break' doesn't do it?Sent from my BlackBerry portable Babbage Device
From: inkwizyt...@gmail.comSent: Sunday, June 12, 2016 7:13 AMTo: ISO C++ Standard - Future ProposalsReply To: std-pr...@isocpp.orgSubject: Re: [std-proposals] Re: statement folding on variadic templates parameters pack
I was referring to switch after applying `for constexpr`. Some thing like that:And in second switch today compilers do that I mentioned (if `OpId` create proper range). `for constexpr` is not trivial but if it support `case` then getting jump table from `switch` and `for constexpr` will be trivial.switch (x)
{
for constexpr (typename T : Op...)
{
case T::OpId: T::OpFunc(); break;
}
break;
}
//After `for constexpr` "unroll"
switch (x)
{
case Op0::OpId: Op0::OpFunc(); break;
case Op1::OpId: Op1::OpFunc(); break;
case Op2::OpId: Op2::OpFunc(); break;
case Op3::OpId: Op3::OpFunc(); break;
}
switch (x)
{
case Op0::OpId: Op0::OpFunc(); goto finalFor;
case Op1::OpId: Op1::OpFunc(); goto finalFor;
case Op2::OpId: Op2::OpFunc(); goto finalFor;
case Op3::OpId: Op3::OpFunc(); goto finalFor;
finalFor: break;
}
On Sunday, June 12, 2016 at 5:39:41 PM UTC+2, Tony V E wrote:I was referring to switch after applying `for constexpr`. Some thing like that:And in second switch today compilers do that I mentioned (if `OpId` create proper range). `for constexpr` is not trivial but if it support `case` then getting jump table from `switch` and `for constexpr` will be trivial.switch (x)
{
for constexpr (typename T : Op...)
{
case T::OpId: T::OpFunc(); break;
}
break;
}
//After `for constexpr` "unroll"
switch (x)
{
case Op0::OpId: Op0::OpFunc(); break;
case Op1::OpId: Op1::OpFunc(); break;
case Op2::OpId: Op2::OpFunc(); break;
case Op3::OpId: Op3::OpFunc(); break;
}
How do I break from the for constexpr, if 'break' doesn't do it?
`break` work same as in normal `for`, critical is last `break` in original `switch` because `for constexpr` breaks there. I skip one transformation stage that show it:switch (x)
{
case Op0::OpId: Op0::OpFunc(); goto finalFor;
case Op1::OpId: Op1::OpFunc(); goto finalFor;
case Op2::OpId: Op2::OpFunc(); goto finalFor;
case Op3::OpId: Op3::OpFunc(); goto finalFor;
finalFor: break;
}
On Sun, Jun 12, 2016 at 10:22 AM, <inkwizyt...@gmail.com> wrote:On Sunday, June 12, 2016 at 5:39:41 PM UTC+2, Tony V E wrote:How do I break from the for constexpr, if 'break' doesn't do it?
`break` work same as in normal `for`, critical is last `break` in original `switch` because `for constexpr` breaks there. I skip one transformation stage that show it:switch (x)
{
case Op0::OpId: Op0::OpFunc(); goto finalFor;
case Op1::OpId: Op1::OpFunc(); goto finalFor;
case Op2::OpId: Op2::OpFunc(); goto finalFor;
case Op3::OpId: Op3::OpFunc(); goto finalFor;
finalFor: break;
}No, that doesn't work. I think Tony has a point.Sergey's half-proposal wants to make "break" and "continue" inside a "for constexpr" act exactly like "break" and "continue" inside a non-constexpr "for":for constexpr(typename T : Ts...) {do_something<T>();if constexpr(is_same_v<T, nullptr_t>) break;}This code would generate an unrolled loop of do_something<T>()s for each T in Ts... up to and including the first instance of nullptr_t. As soon as it generated do_something<nullptr_t>(), it would break and stop iterating over Ts....You can't have break work this way (Sergey's way) and also have it work for outer switch statements; you have to pick one. And I think you've picked the wrong one, for two reasons:First, Ed Catmur has shown that if what you really want is a way to pack-expand switch cases, there might be more natural syntaxes to express that.// Natively (this syntax strikes me as ungraceful, I admit)switch (x) {
case Op::OpId: { Op::OpFunc(); break; }...
}
// Or via some sort of library templateauto switch_ = make_switch(make_switch_case( Op::OpId, [](){ return Op::OpFunc(); } )...);auto result = switch_.on(x) + switch_.on(y);template<class X, class... Cs>auto switch_on(X&& x, Cs&&... cases) {return make_switch(std::forward<Cs>(cases)...).on(std::forward<X>(x));}
Second, I think you're doing the same thing I did at first, which is to treat "for constexpr" (or whatever) as a pack-expansion primitive in the same vein as fold-expressions, whereas what it really wants to be is a compile-time control-flow (code-generation) primitive in the same vein as "if constexpr" and function templates. If you stop thinking of it as physically duplicating/unrolling its contents (the way a fold-expression does), then you no longer have to worry about what unrolling a "break" or "continue" statement would mean.I found the docs for Boost.Switch. If I get the time, I'll try to write up a modern library implementation of the above make_switch and post it here. I'm sure it's been done before, of course, so if someone beats me to it that's great.–Arthur
switch (0)
{
for (;;)
{
f();
case 0: g(); break;
}
h();
}
switch (0)
{
for constexpr (int I : II...)
{
f<I>()
case I: g<I>(); break;
}
h();
}for constexpr (typename T : TT...)
{
T x; //this will require creating separate variable for each iteration, but they lifetime not overlap.
}
I found the docs for Boost.Switch. If I get the time, I'll try to write up a modern library implementation of the above make_switch and post it here. I'm sure it's been done before, of course, so if someone beats me to it that's great.
#define SWITCH_CASE(Z,N,_) \
case std::tuple_element_t<N, std::tuple<std::integral_constant<T, C>...>>{} : \
return std::forward<F>(f)( \
std::tuple_element_t<N, std::tuple<std::integral_constant<T, C>...>>{}); \
//
#define SWITCH_SPECIALIZATION(Z,N,_) \
template<class T, T... C, class F> \
struct Switch<N, std::integer_sequence<T, C...>, F> { \
using R = std::common_type_t<std::result_of_t<F(std::integral_constant<T, C>)>...>; \
R operator()(T t, F&& f) { \
switch(t) { BOOST_PP_REPEAT_ ## Z(N, SWITCH_CASE, nil) } \
} \
/* ... */ \
};
BOOST_PP_REPEAT(SWITCH_MAX, SWITCH_SPECIALIZATION, nil)
On Sunday, June 12, 2016 at 11:25:50 AM UTC+1, Ville Voutilainen wrote:On 12 June 2016 at 13:23, <inkwizyt...@gmail.com> wrote:
> This code should be fast as manually written switch, it would be trivial for
> compiler to create jump table to have best performance.
I'm sure the open-source compilers will happily accept an extension
patch that shows how trivial it is.
For what is worth, you can abuse GCC statement expressions plus variadics to get variadic expansion of switch statements. Although 'case' in statement expressions is explicitly documented not to be supported, it does work in practice (nothing I would use in production of course).
So, while I wouldn't call it trivial, it seems to me that it souldn't be too hard to implement the extension in GCC, and you could even consider statement expressions as existing practice.
On Sun, Jun 12, 2016 at 6:17 PM, Edward Catmur <e...@catmur.co.uk> wrote:
On Sunday, 12 June 2016 20:32:22 UTC+1, Arthur O'Dwyer wrote:I found the docs for Boost.Switch. If I get the time, I'll try to write up a modern library implementation of the above make_switch and post it here. I'm sure it's been done before, of course, so if someone beats me to it that's great.Too many times to count, I'm sure! I decided to take another go at it, trying to conform to the Boost.Switch interface but using modern techniques, using only the standard library and Boost.Preprocessor for iteration. My implementation is a little under 60 lines: https://gist.github.com/ecatmur/7f5f8b44e70f414742e4eae1efdd0ca7
Interesting! My implementation ended up at twice the length of yours, but IMHO has a nicer user interface than Boost.Switch.The "apply" example in my case looks like this: extra boilerplate to deal with making index sequences, but the tradeoff is that my interface allows you to switch on things that aren't consecutive integers.
The other tradeoff is that my Clang isn't smart enough to turn this into a switch. It will go as far as inlining everything into an if-else chain (no function calls), but it won't make a jump table. Yes, this kind of defeats the purpose; but I'm holding out hope that it's a Quality of Implementation issue rather than a fundamental flaw.
The "enum to integer" example in your gist doesn't compile on my Clang because it is trying to instantiate std::integer_sequence<E, ...> where E (being an enum) is not an integral type; I think you have to add some boilerplate involving std::conditional_t<std::is_enum_v<E>, std::underlying_type_t<E>, E> and some explicit casts, at which point it doesn't even seem worth it anymore.
Having to create an entity of the form foo<..., E::A, E::B, E::C> should have been a red flag anyway, IMHO.
On 13 Jun 2016 3:14 p.m., "'Edward Catmur' via ISO C++ Standard - Future Proposals" <std-pr...@isocpp.org> wrote:
>
> On Mon, Jun 13, 2016 at 10:33 AM, Giovanni Piero Deretta <gpde...@gmail.com> wrote:
>>
>> On Sunday, June 12, 2016 at 11:25:50 AM UTC+1, Ville Voutilainen wrote:
>>>
>>> On 12 June 2016 at 13:23, <inkwizyt...@gmail.com> wrote:
>>> > This code should be fast as manually written switch, it would be trivial for
>>> > compiler to create jump table to have best performance.
>>>
>>>
>>> I'm sure the open-source compilers will happily accept an extension
>>> patch that shows how trivial it is.
>>
>>
>> For what is worth, you can abuse GCC statement expressions plus variadics to get variadic expansion of switch statements. Although 'case' in statement expressions is explicitly documented not to be supported, it does work in practice (nothing I would use in production of course).
>
>
> That's amazing! I couldn't get it to work in any version after 4.9.2, though; is there a trick I'm missing?
>
> switch (i) {
> ([](...){})(({ case I : g<I>(), ({ return; }), 0; })... );
> }
>
It's been a while; I remember not needing the lambda but I had to split the line in multiple separate statement expressions. It did require a lot of trials and errors to find a formulation that GCC would accept. I remember never getting rerun to work (only break).
I think that `for constexpr` should be allowed to extracting parameters from templates, some thing like that:using type = std::tuple<int, long long, char>;
int main()
{
for constexpr (typename T : type) //without `...`
{
f<T>();
} //equal: `f<int>(); f<long long>(); f<char>();`
}
This will allow usage outside of template function and simplify complex operation:template<typename T>
using MetaFunc = std::tuple<int, T, T, char>;
int main()
{
for constexpr (typename T : MetaFunc<long>)
{
f<T>();
} //equal: `f<int>(); f<long>(); f<long>(); f<char>();`
for constexpr (typename T : MetaFunc<char>)
{
for constexpr (typename TT : MetaFunc<T>)
{
g<T,TT>();
}
}
}
template<typename... T>
using void_t = void;
for constexpr (typename T: void_t<int8_t, int16_t, int32_t, int64_t>) {
do_something();
}
for constexpr (typename T: params_of<void_t<int8_t, int16_t, int32_t, int64_t>>) {
do_something();
}
template<typename... L>
struct test {
using params = L;
};
int main() {
for constexpr (typename T, test<char, int>::params) {
std::cout << sizeof(T) << std::endl;
}
}
`break` work same as in normal `for`, critical is last `break` in original `switch` because `for constexpr` breaks there. I skip one transformation stage that show it:switch (x)
{
case Op0::OpId: Op0::OpFunc(); goto finalFor;
case Op1::OpId: Op1::OpFunc(); goto finalFor;
case Op2::OpId: Op2::OpFunc(); goto finalFor;
case Op3::OpId: Op3::OpFunc(); goto finalFor;
finalFor: break;
}No, that doesn't work. I think Tony has a point.Sergey's half-proposal wants to make "break" and "continue" inside a "for constexpr" act exactly like "break" and "continue" inside a non-constexpr "for":
for constexpr(typename T : Ts...) {do_something<T>();if constexpr(is_same_v<T, nullptr_t>) break;}This code would generate an unrolled loop of do_something<T>()s for each T in Ts... up to and including the first instance of nullptr_t. As soon as it generated do_something<nullptr_t>(), it would break and stop iterating over Ts....
I was referring to switch after applying `for constexpr`. Some thing like that:And in second switch today compilers do that I mentioned (if `OpId` create proper range). `for constexpr` is not trivial but if it support `case` then getting jump table from `switch` and `for constexpr` will be trivial.switch (x)
{
for constexpr (typename T : Op...)
{
case T::OpId: T::OpFunc(); break;
}
break;
}
//After `for constexpr` "unroll"
switch (x)
{
case Op0::OpId: Op0::OpFunc(); break;
case Op1::OpId: Op1::OpFunc(); break;
case Op2::OpId: Op2::OpFunc(); break;
case Op3::OpId: Op3::OpFunc(); break;
}
My personal opinion on such feature: there should be separate syntax to grab template parameters likefor constexpr (typename T: params_of<void_t<int8_t, int16_t, int32_t, int64_t>>) {
do_something();
}
but it require some extra work on how this should actually work. In D it's possible to implement params_of in library code since it's possible to typedef parameters pack and then use it when needed. The following C++ code rewritten in D works as expected:template<typename... L>
struct test {
using params = L;
};
int main() {
for constexpr (typename T, test<char, int>::params) {
std::cout << sizeof(T) << std::endl;
}
}
From my personal point of view this way to have typelist outside of template is much better then grabbing parameters from some intermediate template which is needed only to enlist types you need. This approach also allows to have better of your MetaFunc template without issues I've mentioned above.
My idea was that we have concrete template type with some parameters. Then `for constexpr` will extract parameters of this template. This is why template alias are skipped. They can never by concrete. They more like type to type functions that templates.
template<typename... L>
using tuple = detail::tuple_impl<sizeof...(L), L...>;
template<typename... L>
struct types_list {};
template<typename... L>
struct test {
using params = L;
};
int main() {
for constexpr (typename T, test<char, int>::params) {
std::cout << sizeof(T) << std::endl;
}
}
вторник, 14 июня 2016 г., 5:18:24 UTC+6 пользователь inkwizyt...@gmail.com написал
My idea was that we have concrete template type with some parameters. Then `for constexpr` will extract parameters of this template. This is why template alias are skipped. They can never by concrete. They more like type to type functions that templates.
Ok. Got the point on alias templates. But it actually means that this feature is only usable when it's strong guarantie that template you are using not an alias to nontrivial implementation. For example there might be some problems with your examples if std::tuple is defined in such way:template<typename... L>
using tuple = detail::tuple_impl<sizeof...(L), L...>;
grbbing template by for constexpr seems only be usable together with some template class designed to be used for grabbing parameters. Something like:
for constexpr (typename TT : zip_t<take_t<10, std::tuple<T...>>, std::make_index_sequence_t<10>>)
{
typename TT::first a; //type from T pack, but only first 10
int a[TT::second::value]; //value from make_index_sequence_t
}
template<typename... L>
struct types_list {};
which has strong guarantie that no extra types or nontype argument will apear when it's used in "for constexpr". I think that D-like code I've posted above solves the same issue in much better way:template<typename... L>
struct test {
using params = L;
};
int main() {
for constexpr (typename T, test<char, int>::params) {
std::cout << sizeof(T) << std::endl;
}
}
Thre are two different language constructs designed for their own purpuses which allows to have type list to iterate over in nontemplate function when those features are used together. From my personal point of view this way is much better than grabbing template parameters unless there are some use cases which are not just "some way to have iterable typelist in non-template code". Do you have some example usecases for the feature?
-- Sergey