21

if constexpr is a big step for getting rid of preprocessor in C++ programs. However it works only in functions - like in this example:

enum class OS
{
    Linux,
    MacOs,
    MsWindows,
    Unknown
};

#if defined(__APPLE__)
constexpr OS os = OS::MacOs;
#elif defined(__MINGW32__)
constexpr OS os = OS::MsWindows;
#elif defined(__linux__)
constexpr OS os = OS::Linux;
#else
constexpr OS os = OS::Unknown;
#endif

void printSystem()    
{
    if constexpr (os == OS::Linux)
    {
        std::cout << "Linux";
    }
    else if constexpr (os == OS::MacOs)
    {
        std::cout << "MacOS";
    }
    else if constexpr (os == OS::MsWindows)
    {
        std::cout << "MS Windows";
    }
    else
    {
        std::cout << "Unknown-OS";
    }
}

But dreams about getting rid of preprocessor are not quite satisfied - because the following examples do not compile:

1 Cannot use it in class definition to define some members of class differently:

class OsProperties
{
public:
    static void printName()
    {
        std::cout << osName;
    }
private:
    if constexpr (os == OS::Linux)
    {
        const char* const osName = "Linux";
    }
    else if constexpr (os == OS::MacOs)
    {
        const char* const osName = "MacOS";
    }
    else if constexpr (os == OS::MsWindows)
    {
        const char* const osName = "MS Windows";
    }
    else
    {
        const char* const osName = "Unknown";
    }
};

2 Nor it works for not class-scope (like global scope):

if constexpr (os == OS::Linux)
{
    const char* const osName = "Linux";
}
else if constexpr (os == OS::MacOs)
{
    const char* const osName = "MacOS";
}
else if constexpr (os == OS::MsWindows)
{
    const char* const osName = "MS Windows";
}
else
{
    const char* const osName = "Unknown";
}

I am (almost) sure this is per C++17 specification that if constexpr works only within function bodies - but my questions are:

Q1 How to achieve the similar effect like if-constexpr in functions - for class and global scope in C++1z/C++14? And I am not asking here for yet another explanation of template specialization... But something that has similar simplicity as if constexpr...

Q2 Are there any plan to extend C++ for the above mentioned scopes?

PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • 3
    If you only want to change the initializer, throw that into a (possibly `constexpr`) function. If you want to do something more complicated, that's just the old `static if` proposal which went down in flames. – T.C. Dec 13 '16 at 10:38
  • You don't even need `constexpr if` in this case: https://godbolt.org/g/RvLUw1 – Simon Kraemer Dec 13 '16 at 10:41
  • control-statements/blocks are **not** allowed in the class-scope and at namespace-level. It has nothing to do with `constexpr`. – Nawaz Dec 13 '16 at 10:45
  • Or like https://godbolt.org/g/z2F6Eu – Simon Kraemer Dec 13 '16 at 10:45
  • BTW: I hope that `constexpr if` won't be allowed at the scopes you propose. That would introduce the problems that macros have. – Simon Kraemer Dec 13 '16 at 10:56
  • @T.C. I understand this `static if` was somehow similar to D language: http://stackoverflow.com/questions/12369978/why-use-static-if-in-d . Why C++ committee was against, I guess it was not simple desire to differ from D? – PiotrNycz Dec 13 '16 at 11:28
  • @SimonKraemer Yes, you are right for the current examples. I'll be thinking about examples that cannot be solved so easily. I mean - I really waiting for more general answers - not just to my "not so perfect" examples... – PiotrNycz Dec 13 '16 at 11:30
  • @SimonKraemer That would be valuable answer if you provide more detailed reasoning why you think it wouldn't be better than macros... – PiotrNycz Dec 13 '16 at 11:31
  • 1
    Why not to use template specialization for that? [example](http://melpon.org/wandbox/permlink/Yl03T1CQnX8jFV96) – W.F. Dec 13 '16 at 11:59
  • 1
    @W.F. Sure it solves - but OP is "not asking here for yet another explanation of template specialization... " I mean - I want something that works in so simple way as `if constexpr` but on non-function-body levels... – PiotrNycz Dec 13 '16 at 13:50
  • 1
    If I have a class that is 1000+ lines long, then using template specialization to add another class member for my constexpr bool feature flag is an abomination... I just... want... static if. – Nathan Doromal Oct 14 '19 at 16:05
  • As it stands, this example doesn't make what you're asking for clear -- it can be solved handily with `if constexpr`. You need code that's closer to what you're trying to achieve. – Spencer Jan 10 '22 at 14:24
  • @Spencer this second example is about global scope. I made it clear now. – PiotrNycz Jan 10 '22 at 15:36
  • @PiotrNycz You can use a constexpr function (with no parameters) that uses `if constexpr` on the global `os` variable to get what you want. – Spencer Jan 10 '22 at 17:51
  • @Spencer is your advice similar/identical to what I provided in my own answer to my question https://stackoverflow.com/a/41147457/1463922 ? – PiotrNycz Jan 11 '22 at 07:59

3 Answers3

17

How to achieve the similar effect like if-constexpr in functions - for class and global scope in C++1z/C++14? And I am not asking here for yet another explanation of template specialization...

You basically just said, "I want template specialization, but without all that pesky template specialization."

if constexpr is the tool for making the behavior of functions change based on compile-time constructs. Template specialization is the tool that C++ provides for making definitions change based on compile-time constructs. It is the only tool C++ provides for this functionality.

Now for your simplistic case of initializing a variable, you can always create and call a lambda. C++17 offers constexpr support for lambdas, and a lambda would be able to use if constexpr to decide what value to return.

Are there any plan to extend C++ for the above mentioned scopes?

No. Here are all of the proposals, and none of the ones from the past couple of years delve into this domain.

And it's highly unlikely they ever will.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Do you know maybe why mentioned by @T.C. "the old static if proposal which went down in flames"? Is this document more or less describe it: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3613.pdf ? – PiotrNycz Dec 13 '16 at 16:23
  • 1
    @PiotrNycz: Yes, that'd be a pretty decent overview. – Nicol Bolas Dec 13 '16 at 16:29
  • 1
    "if constexpr" is not a tool for avoiding macros per se -- it rather helps us to avoid SFINAE and/or tag dispatch tricks, writing much simpler code when customizing behavior of functions. So that's really a pity that committee didn't take another step forward in that direction: allow us to avoid similar code obfuscation when we want conditionally define some member fields or base classes in a class template. That is kind of possible (using inheritance), but the resulting code is horrible and nearly impossible to maintain. I wish they had looked on "static if" in D. – Taras Jan 25 '19 at 10:44
  • You really miss the mark with your first sentence. I think the entire point of this is to *avoid* specializing the template, not because it's "pesky" but because it can (a) cause conflicts with other specializations, (b) prevents you from keeping everything the same (at the very least you'll have to derive an extra class), and (c) because it's not even possible in every case (like partially specializing nested classes). – user541686 Feb 26 '21 at 01:13
  • @user541686 You can definitely use `if constexpr` inside of member function to reduce (or even eliminate) the number of specializations. – Spencer Jan 10 '22 at 14:17
4

An index type:

template<std::size_t I>
using index = std::integral_constant<std::size_t, I>;

first_truth takes a set of compile-time bools and says what the index of the first one is at compile time. If you pass it N compile-time bools, it returns N if all are false:

constexpr index<0> first_truth() { return {}; }
template<class...Rest>
constexpr index<0> first_truth(std::true_type, Rest...) { return {}; }
template<class...Rest>
constexpr auto first_truth(std::false_type, Rest...rest) {
  return index<first_truth( rest... )+1>{};
}

dispatch takes a set of compile-time bools and returns a lambda. This lambda returns via perfect forwarding the first element that matches the first true compile time bool:

template<class...Bools>
constexpr auto dispatch(Bools...bools) {
  constexpr auto index = first_truth(bools...);

  return [](auto&&...fs){
    return std::get< decltype(index){} >(
      std::forward_as_tuple( decltype(fs)(fs)... )
    );
  };
}

A compile time bool type:

template<bool b>
using bool_t = std::integral_constant<bool, b>;
template<bool b>
bool_t<b> bool_k{};

Now we solve your problem:

const char* const osName = 
  dispatch(
    bool_k<os == OS::Linux>,
    bool_k<os == OS::MacOs>,
    bool_k<os == OS::MsWindows>
  )(
    "Linux",
    "MacOS",
    "MS Windows",
    "Unknown"
  );

which should approximate a compile-time switch. We could tie the bools more closely to the arguments with a bit more work.

Code not compiled, probably contains tpyos.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • That's pretty cool - looks like good start point. I am planning also to investigate boost mpl,fusion,hana to find more general way though - I want to "emulate" preprocessor during compilation - have different function, types - not only values. One comment: why not `std::bool_constant` instead of `bool_t`? – PiotrNycz Dec 14 '16 at 10:08
  • @PiotrNycz Because I'm not used to having `std::bool_constant`. – Yakk - Adam Nevraumont Dec 14 '16 at 11:26
3

how to define different types based on some compile time constant w/o template specialization?

Here it is:

constexpr auto osPropsCreate()
{
    if constexpr (os == OS::Linux) {
        struct Props { const char* name; int props1; using handle = int; }; 
        return Props{"linux", 3};
    } else if constexpr (os == OS::MacOs) {
        struct Props { const char* name; using handle = float; }; 
        return Props{"mac"};
    } else if constexpr (os == OS::MsWindows) {
        struct Props { const char* name; using handle = int; }; 
        return Props{"win"};
    } else
        return;  
}

using OsProps = decltype(osPropsCreate());
constexpr OsProps osProps = osPropsCreate();

As you can see - I used the new construction if constexpr to produce from some "implementation" function the type that depends on compile time constant. It is not as easy to use as static if in D language - but it works - I can do it:

int linuxSpecific[osProps.props1];
int main() {
    std::cout << osProps.name << std::endl;
    OsProps::handle systemSpecificHandle;
}

Next thing - define different functions depending on compile time constant:

constexpr auto osGetNameCreate() {
    if constexpr (os == OS::Linux) {
        struct Definition {
            static constexpr auto getName() {
                return "linux";
            }
        };
        return Definition::getName;
    } else if constexpr (os == OS::MacOs) {
        // we might use lambda as well
        return [] { return "mac"; };
    } else if constexpr (os == OS::MsWindows) {
        struct Definition {
            static constexpr auto getName() {
                return "win";
            }
        };
        return Definition::getName;
    } else
        return;
}


constexpr auto osGetName = osGetNameCreate();

int main() {
    std::cout << osGetName() << std::endl;
} 

Actually, they can be either function-like objects (functors) or static member functions from nested classes. This does not matter - one have full freedom to define different things for different compile time constants (OS type in this case). Notice, that for unknown system we just return void - it will cause compilation error for unknown system...


Answering to second question:

The first answer provide it with reasoning in comments (link). My interpretation is that C++ standard committee is not ready for that change. Maybe competing with D will/would be a good reason to raise this subject once again...

PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • 1
    The version you want is, [to quote Richard Smith (Clang dev and project editor of the C++ standard)](https://groups.google.com/a/isocpp.org/d/msg/std-proposals/bX96720ccmU/0bJjSlQxEhMJ), "fundamentally incompatible with the template model used by at least two major implementations". The chance of that happening is about the same as a snowball's chance in hell. – T.C. Dec 14 '16 at 23:27
  • "*But because these all are capture-less lambdas - I am pretty sure every of them can be cast to raw function pointer. *" The lambda object may be `constexpr`, but the *lambda function* itself is not. You need a C++17 feature for that and explicitly declare them with `constexpr`. But really, there's no need for the indirection; just call the lambda when initializing the variable and store it directly. – Nicol Bolas Dec 15 '16 at 01:42
  • @NicolBolas With `if constexpr` I can return from function whatever I really want - function pointer, lambda - even `void` for unknown system. My answer is just to show that I can use current C++17 `if constexpr` to define types and functions depending on compile time constant. This requires tricky code - so I still think that direct language support would help to keep code clean. – PiotrNycz Dec 15 '16 at 09:35