Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Something like "if constexpr" but for class definition

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?

like image 817
PiotrNycz Avatar asked Dec 13 '16 10:12

PiotrNycz


People also ask

Which is false about constexpr?

Short answer: static_assert(false) should never appear in a constexpr if expression, regardless of whether it's in a template function or whether it's in the discarded branch.

What is if constexpr?

Constexpr if If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.


2 Answers

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.

like image 93
Nicol Bolas Avatar answered Oct 21 '22 22:10

Nicol Bolas


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.

like image 45
Yakk - Adam Nevraumont Avatar answered Oct 21 '22 22:10

Yakk - Adam Nevraumont