javaLover javaLover - 3 months ago 19
C++ Question

mimic "if constexpr" behavior, impossible?

My compiler doesn't support

if constexpr
, but I am captivated by its benefit.

I must have it - even it might be a fake one.

This code is my attempt to mimic
if constexpr
behavior.

The objective is to make the lines (###) appear in only 1 function :-

#include <iostream>
using namespace std;

template<bool Flag,typename F> constexpr typename std::enable_if<!Flag, void>::type iter_(F f,int i1){
f(i1); //No! The compiler still tried to compile even Flag=true
}
template<bool Flag,typename F> constexpr typename std::enable_if<Flag, void>::type iter_(F f,int i1){ }
template<bool Flag,typename F> constexpr typename std::enable_if<Flag, void>::type iter_(F f,int i1,int i2){
f(i1,i2); //No! The compiler still tried to compile even Flag=false
}
template<bool Flag,typename F> constexpr typename std::enable_if<!Flag, void>::type iter_(F f,int i1,int i2){}

template<bool Flag,typename F> constexpr void fff( F f ){
for(int n=0;n<5;n++){//fake loop, the real situation is very complex
//### some horror code appeared here, but omitted
if(Flag){//attempt to mimic "if constexpr"
iter_<true>(f,1,2);
}else{
iter_<false>(f,3);
}
}
}


This is its usage:-

template<typename F> constexpr void fff1( F f ){fff<false>(f);} //usage
template<typename F> constexpr void fff2( F f ){fff<true>(f);} //usage

int main() {
// your code goes here
auto f1=[&](int a){
cout<<a<<" ";
};
auto f2=[&](int a,int b){
cout<<a<<" "<<b<<endl;
};
fff1(f1);
fff2(f2);
return 0;
}


I got compile error :

prog.cpp: In instantiation of 'constexpr typename std::enable_if<Flag, void>::type iter_(F, int, int) [with bool Flag = true; F = main()::<lambda(int)>; typename std::enable_if<Flag, void>::type = void]':
prog.cpp:16:18: required from 'constexpr void fff(F) [with bool Flag = false; F = main()::<lambda(int)>]'
prog.cpp:22:61: required from 'constexpr void fff1(F) [with F = main()::<lambda(int)>]'
prog.cpp:33:9: required from here
prog.cpp:9:3: error: no match for call to '(main()::<lambda(int)>) (int&, int&)'
f(i1,i2);
^
prog.cpp:9:3: note: candidate: void (*)(int) <conversion>
prog.cpp:9:3: note: candidate expects 2 arguments, 3 provided


From the error, it is clear to me that even when a function has std::enable_if[ effective FALSE],

the compiler still compiled code that inside the function.
- That is bad.

Which parts do I have to edit?

... or are there any alternatives?

... or is it not possible to mimic
if constexpr
at all (that is the reason why it is introduced finally)?

Answer

Read this: http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0128r1.html

Can't we do this with existing language features?

John Spicer suggested in c++std-ext-17099 that polymorphic lambdas combined with a decision-making template would provide an adequate facility without a need to add new language features. The invocation of that decision-making template looks roughly like this:

template <int arg, typename ... Args> int do_something(Args... args) {
    return static_if<sizeof...(args)>::get(
        [](auto x, auto y) { return x+y; },
        [](auto x) { return *x; })(args...);
}

Now, in comparison, with the proposed language facility, we do

template <int arg, typename ... Args> int do_something(Args... args) {
    constexpr if (sizeof...(args)) {
        return (args + ...);
    } constexpr_else {
        return *args...;
    }
}

Now this is sort of a an alternative. It gets more complicated if the different branches return different types.

Moreover,

I must point out some things here:

  • I can return, break, continue and goto from within a constexpr if block. I can not do that from within the lambda.

  • While I am a big proponent of using lambdas to create new control facilities, I find the constexpr if solution infinitely more readable.

Further,

Richard Smith explained the following:

Right, when a function template is instantiated, all of the declarations/statements/expressions within it are instantiated, and that includes pieces inside local classes, generic lambdas, and so on.

This instantiation of generic lambda bodies is in fact necessary for our language semantics -- computing the captures of a generic lambda within a function template specialization relies on us having already instantiated the complete closure type and its call operator template to the point that we know where the odr-uses are within the non-dependent full-expressions within the body.

In contrast, the intent of constexpr if is that the branch not taken is not instantiated.

Comments