auto x = (std::is_integral_v<T>) ? A{} : B{};
auto x = (std::is_integral_v<T>) ? A{} : B{};
This would be like if constexpr with implicit constexpr determinded by
the compiler.
I had the same idea the other day :)
constexpr-if is explicit, why should the the ternary be different?
auto x = std::is_integral_v<T> constexpr ? A{} : B{};
On Sat, May 20, 2017 at 3:11 PM, Marc Mutz <marc...@kdab.com> wrote:
auto x = (std::is_integral_v<T>) ? A{} : B{};
This would be like if constexpr with implicit constexpr determinded by
the compiler.
I had the same idea the other day :)
constexpr-if is explicit, why should the the ternary be different?
auto x = std::is_integral_v<T> constexpr ? A{} : B{};
Seems not so hard?auto x = std::conditional_t<std::is_integral_v<T>, A, B>{};
auto x = runtime_condition ? A{} : B{};
x.doit();
auto f = [&](auto x) { x.doit(); };
runtime_condition ? f(A{}) : f(B{});
auto x = runtime_condition ? A{} : B{};
The generic lambda is instantiated two times, for each branch. Depending on runtime_condition,
only one instance is executed. The statement
auto x = runtime_condition ? A{} : X{};
could introduce the following code inside the current scope twice, like the generic lambda did. Ones for each type.
Depending on the runtime_condition, the code pointer is moved to the first or the second instantiaton with a jump.
The following C++ code snippet describes roughly how the generated code could look like.#include <iostream>
struct A{ void doit(){ std::cout << "A::doit" << std::endl; } };
struct B{ void doit(){ std::cout << "B::doit" << std::endl; } };
int main()
{
auto runtime_condition = true;
if(runtime_condition)
goto ternary_branch_1;
else
goto ternary_branch_2;
ternary_branch_1:
A __instance1__;
__instance1__.doit();
return 0;
ternary_branch_2:
B __instance2__;
__instance2__.doit();
return 0;
}
A::doit():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
nop
pop rbp
ret
B::doit():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
nop
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov BYTE PTR [rbp-1], 1
cmp BYTE PTR [rbp-1], 0
je .L9
nop
lea rax, [rbp-2]
mov rdi, rax
call A::doit()
mov eax, 0
jmp .L8
.L9:
nop
lea rax, [rbp-3]
mov rdi, rax
call B::doit()
mov eax, 0
.L8:
leave
ret
void foo(int n) { /* A: Do something before the loop */ for (int i = 0; i < n; ++i) { /* B: Do something */ auto x = runtime_condition() ? A{} : B{}; /* C: Do some other things with x */ } /* D: Do something after the loop */}
template <class T>void bar(int i, int n, T x) { /* C: Do some other things with x */ ++i; if (i < n) { /* B: Do something */ if (runtime_condition()) { bar(i, n, A{}); } else { bar(i, n, B{}); } }}
void foo(int n) { /* A: Do something before the loop */ if (0 < n) { /* B: Do something */ if (runtime_condition()) { bar(1, n, A{}); } else { bar(1, n, B{}); } } /* D: Do something after the loop */}
auto x0 = runtime_condition_0() ? A0{} : B0{};auto x1 = runtime_condition_1() ? A1{} : B1{};// ...auto xn = runtime_condition_n() ? An{} : Bn{};
/* Declares the common expressions required in the context */proxy P { void f_0(); double f_1(int, int);};
void foo(int n) { /* A: Do something before the loop */ for (int i = 0; i < n; ++i) { /* B: Do something */ Wrapper<P> x(runtime_condition() ? Wrapper<P>(A{}) : Wrapper<P>(B{})); P p = x.get_proxy(); /* C: Do some other things with p */ } /* D: Do something after the loop */}
If the condition is not "constexpr", and the types are determined at runtime, the "proxies" and the "wrappers" can be used to solve this problem. (https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/kvkgsHM6wFQ)
struct A{ void doit(){ } };
struct B{ void doit(){ } };
int main()
{
auto runtime_condition = true;
for(int i = 0; i < 100; i++) {
auto x = runtime_condition ? A{} : B{};
x.doit();
/* do something without x */
x.doit()
/* do something without x */
}
return 0;
}
struct A{ void doit(){ } };
struct B{ void doit(){ } };
int main()
{
auto runtime_condition = true;
for(int i = 0; i < 100; i++) {
if(runtime_condition)
goto ternary_branch_1;
else
goto ternary_branch_2;
ternary_branch_1:
A __instance1__;
__instance1__.doit();
goto ternary_end_1;
ternary_branch_2:
B __instance2__;
__instance2__.doit();
ternary_end_1:
/* do something without x */;
ternary_branch_3:
__instance1__.doit();
goto ternary_end_2;
ternary_branch_4:
__instance2__.doit();
ternary_end_2:
/* do something without x */;
}
return 0;
}
A::doit():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
nop
pop rbp
ret
B::doit():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
nop
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov BYTE PTR [rbp-5], 1
mov DWORD PTR [rbp-4], 0
.L11:
cmp DWORD PTR [rbp-4], 99
jg .L4
cmp BYTE PTR [rbp-5], 0
je .L13
nop
lea rax, [rbp-6]
mov rdi, rax
call A::doit()
jmp .L9
.L13:
nop
lea rax, [rbp-7]
mov rdi, rax
call B::doit()
.L9:
lea rax, [rbp-6]
mov rdi, rax
call A::doit()
nop
add DWORD PTR [rbp-4], 1
jmp .L11
.L4:
mov eax, 0
leave
ret
I revisited your statement, that this isn't possible, because the type would be determined at runtime. I have to disagree, now. It is possible at compile time.
auto f = [&](auto x) { x.doit(); };
runtime_condition ? f(A{}) : f(B{});
If the type cannot be deduced at compile time, then runtime overhead is inevitable.
On domingo, 21 de maio de 2017 02:03:45 PDT ma.ka...@web.de wrote:
> The code, following the ternary statement is moved into a generic lambda.
> auto x = runtime_condition ? A{} : B{};
>
> The generic lambda is instantiated two times, for each branch. Depending on
> runtime_condition,
> only one instance is executed. The statement
You do realise this grows exponentially, right?
auto x = runtime_condition ? A{} : B{};
auto y = runtime_condition ? C{} : D{};
auto z = runtime_condition ? E{} : F{};
x.f() + y.f() + z.f();
There are 8 different branches there to make the above work.
auto x = runtime_condition [[non generic]] ? A{} : B{};
auto x = runtime_condition [[generic]] ? A{} : B{};
struct W{ int doit(){ return 1; } };
struct X{ int doit(){ return 2; } };
struct Y{ int doit(){ return 3; } };
struct Z{ int doit(){ return 4; } };
int example(bool runtime_condition1, bool runtime_condition2) {
int result;
if(!runtime_condition1)
goto ternary_branch_1;
if(!runtime_condition2)
goto ternary_branch_2;
result = W{}.doit() + Y{}.doit();
goto ternary_end;
ternary_branch_1:
if(!runtime_condition2)
goto ternary_branch_3;
result = X{}.doit() + Y{}.doit();
goto ternary_end;
ternary_branch_2:
result = W{}.doit() + Z{}.doit();
goto ternary_end;
ternary_branch_3:
result = X{}.doit() + Z{}.doit();
ternary_end:
return result;
}
W::doit():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8],
rdi
mov eax, 1
pop rbp
ret
X::doit():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8],
rdi
mov eax, 2
pop rbp
ret
Y::doit():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8],
rdi
mov eax, 3
pop rbp
ret
Z::doit():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8],
rdi
mov eax, 4
pop rbp
ret
example(bool, bool):
push rbp
mov rbp, rsp
push rbx
sub rsp, 40
mov edx, edi
mov eax, esi
mov BYTE PTR [rbp-36], dl
mov BYTE PTR [rbp-40], al
movzx eax, BYTE PTR [rbp-36]
xor eax, 1
test al, al
jne .L21
movzx eax, BYTE PTR [rbp-40]
xor eax, 1
test al, al
jne .L22
lea rax, [rbp-28]
mov rdi, rax
call W::doit()
mov ebx, eax
lea rax, [rbp-27]
mov rdi, rax
call Y::doit()
add eax, ebx
mov DWORD PTR [rbp-20], eax
jmp .L14
.L21:
nop
movzx eax, BYTE PTR [rbp-40]
xor eax, 1
test al, al
jne .L23
lea rax, [rbp-26]
mov rdi, rax
call X::doit()
mov ebx, eax
lea rax, [rbp-25]
mov rdi, rax
call Y::doit()
add eax, ebx
mov DWORD PTR [rbp-20], eax
jmp .L14
.L22:
nop
lea rax, [rbp-24]
mov rdi, rax
call W::doit()
mov ebx, eax
lea rax, [rbp-23]
mov rdi, rax
call Z::doit()
add eax, ebx
mov DWORD PTR [rbp-20], eax
jmp .L14
.L23:
nop
lea rax, [rbp-22]
mov rdi, rax
call X::doit()
mov ebx, eax
lea rax, [rbp-21]
mov rdi, rax
call Z::doit()
add eax, ebx
mov DWORD PTR [rbp-20], eax
.L14:
mov eax, DWORD PTR [rbp-20]
add rsp, 40
pop rbx
pop rbp
ret
example(bool, bool):
xor eax, eax
test sil, sil
sete al
test dil, dil
je .L2
add eax, 4
ret
.L2:
add eax, 5
ret
auto x = runtime_condition [[generic]] ? A{} : B{};
auto x = runtime_condition ?? A{} : B{};
This way, the programmer has to tell the compiler that he want to use the generic version. No unexpected exponential growth and no hidden type errors. Maybe this is too much for the upcomming standard. There is no room for such an extension. The list of must have features is already too long. There is not much room left besides Concepts and Modules, anyway. So this will be a proposal for a later standard. I think there are more cases, whrere this technique could be applied. These cases are not explored yet.//generic lambda
[[generic]] [](bool runtime_condition) {
if(runtime_condition) {
return A{};
} else {
return B{};
}
}
// generic function
[[generic]] auto doit(bool runtime_condition) {
if(runtime_condition) {
return A{};
} else {
return B{};
}
}
// generic function with generic ternary operator
[[generic]] auto doit(bool runtime_condition) {
return runtime_condition [[generic]] ? A{} : B{};
}
// generic variable
void doit(bool runtime_condition) {
[[generic]] auto x;
if(runtime_condition) {
x = A{};
} else {
x = B{};
}
}
// generic variable with concept
void doit(bool runtime_condition) {
[[generic]] Iteratable x;
if(runtime_condition) {
x = A{};
} else {
x = B{};
}
}
[[generic]] class X{};
[[generic]] class Y{};
On Tuesday, 23 May 2017 01:07:52 PDT ma.kalbfuss via ISO C++ Standard - Future
Proposals wrote:
> The hiding of type errors and unwanted exponential growth of the code size
> could be avoided by using an attribute or a different operator symbol.
> Please ignore the attribute name and the choosen symbol for now. They are
> only placeholers.
>
> auto x = runtime_condition [[generic]] ? A{} : B{};
>
> or
>
> auto x = runtime_condition ?? A{} : B{};
>
> This way, the programmer has to tell the compiler that he want to use the
> generic version. No unexpected exponential growth and no hidden type
> errors. Maybe this is too much for the upcomming standard. There is no room
> for such an extension. The list of must have features is already too long.
> There is not much room left besides Concepts and Modules, anyway. So this
> will be a proposal for a later standard. I think there are more cases,
> whrere this technique could be applied. These cases are not explored yet.
You're missing the point that the code growth is not optional.
It doesn't matter how you mark this new syntax, the fact that we have a
variable whose type cannot be determined at compile time means that there's
runtime overhead.
auto x = runtime_condition ?? expr1 : expr2;
x.op1();
auto z = some_y;
x.op2(&some_y);
if(runtime_condition)
{
auto x = expr1;
x.op1();
auto z = some_y;
x.op2(&some_y);
}
else
{
auto x = expr2;
x.op1();
auto z = some_y;
x.op2(&some_y);
}
//generic lambda
generic [](bool runtime_condition) {
if(runtime_condition) {
return A{};
} else {
return B{};
}
}
// generic function
generic auto doit(bool runtime_condition) {
if(runtime_condition) {
return A{};
} else {
return B{};
}
}
// generic function with generic ternary operator
generic auto doit(bool runtime_condition) {
return runtime_condition generic ? A{} : B{};
}
// generic variable
void doit(bool runtime_condition) {
generic auto x;
if(runtime_condition) {
x = A{};
} else {
x = B{};
}
}
// generic variable with concept
void doit(bool runtime_condition) {
generic Iteratable x;
if(runtime_condition) {
x = A{};
} else {
x = B{};
}
}
//generic lambda
[](bool runtime_condition)-> generic(auto) {
if(runtime_condition) {
return A{};
} else {
return B{};
}
}
// generic function
generic(auto)
doit(bool runtime_condition) {
if(runtime_condition) {
return A{};
} else {
return B{};
}
}
// generic function with generic ternary operator
generic(auto)
doit(bool runtime_condition) {
return runtime_condition [[generic]] ? A{} : B{};
}
// generic variable
void doit(bool runtime_condition) {
generic(auto) x;
if(runtime_condition) {
x = A{};
} else {
x = B{};
}
}
// generic variable with concept
void doit(bool runtime_condition) {
generic(
Iteratable) x;
if(runtime_condition) {
x = A{};
} else {
x = B{};
}
}