Constexpr If Alternative

Constexpr if alternative

One of pre-C++17 ways is to use partial template specializations, like here:

template <typename T, bool AorB>
struct dummy;

template <typename T, true>
struct dummy {
static void MyFunc() { FunctionA<T>(); }
}

template <typename T, false>
struct dummy {
static void MyFunc() { FunctionB<T>(); }
}

template <typename T>
void Facade() {
dummy<T, MeetsConditions<T>::value>::MyFunc();
}

If you need more, than 2 specializations - you can use enum or integral value, and make specializations for all needed enums.

Another way is to use std::enable_if:

template <typename T>
std::enable_if<MeetsConditions<T>::value, void>::type
MyFunc() {
FunctionA<T>();
}

template <typename T>
std::enable_if<!MeetsConditions<T>::value, void>::type
MyFunc() {
FunctionB<T>();
}

if constexpr vs sfinae

Can one assume that the compiler, on evaluating the if constexpr completely discards the non-verified condition and generate code only for the branch which satisfy the if constexpr condition? Does the standard specifies such a behavior for the compiler?

The standard specifies that, from [stmt.if]:

If the if statement is of the form if constexpr, the value of the condition shall be a contextually converted constant expression of type bool; this form is called a constexpr if statement. If the value of the converted condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity, if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.

The point here is that the discard statement is not instantiated - this is the whole purpose behind if constexpr as a language feature, to allow you to write:

template <typename T0, typename... T>
void print_ifconstexpr(T0&& x, T&&... rest)
{
std::cout << x << std::endl;
if constexpr (sizeof...(T) > 0) {
print_ifconstexpr(std::forward<T>(rest)...);
}
}

You can't do that with a simple if, because that would still require instantiating the substatements - even if the condition can be determined to be false at compile time. A simple if would require the ability to call print_ifconstexpr().

if constexpr will not instantiate the recursive call unless there is something in rest..., so this works.

Everything else follows from the lack of instantiation. There can't be any generated code for the discarded statement.

The if constexpr form is easier to write, easier to understand, and surely compiles faster. Definitely prefer it.


Note that your first example doesn't need the SFINAE at all. This works just fine:

template <typename T>
void print(T&& x)
{
std::cout << x << std::endl;
}

template <typename T0, typename... T>
void print(T0&& x, T&&... rest)
{
std::cout << x << std::endl;
print(std::forward<T>(rest)...);
}

As does:

void print() { }

template <typename T0, typename... T>
void print(T0&& x, T&&... rest)
{
std::cout << x << std::endl;
print(std::forward<T>(rest)...);
}

Is there a constexpr alternative to modf

Is there a constexpr alternative to modf

There is no standard constexpr alternative to modf or related math functions.

but the function is not constexpr. What is the reason for this

Those functions pre-exist constexpr. They even pre-exist C++. As such, when those founctions were specified, they couldn't have been constexpr.

There are no standard constexpr alternatives because such proposal hasn't been accepted into the standard. Such change has been proposed, so this may potentially change in a future standard: P1383

what is the alternative?

Wait for the potential future standard, or do the math without using standard functions.

Equivalent ternary operator for constexpr if?

No, there is no constexepr conditional operator. But you could wrap the whole thing in a lambda and immediately evaluate it (an IIFE):

template<typename Mode>
class BusAddress {
public:
explicit constexpr BusAddress(Address device)
: mAddress([&]{
if constexpr (Mode::write) {
return device.mDevice << 1;
}
else {
return (device.mDevice << 1) | 0x01;
}
}())
{ }
private:
uint8_t mAddress = 0;
};

It may not be the sexiest code ever, but it gets the job done. Note that lambdas are constexpr by default where possible as of N4487 and P0170.

Constexpr alternative to placement new to be able to leave objects in memory uninitialized?

In C++17, you can't.

The current restrictions on what you cannot do in constant expressions include:

  • an assignment expression ([expr.ass]) or invocation of an assignment operator ([class.copy.assign]) that would change the active member of a union;

  • a new-expression;

There really is no way around that.


In C++20, you will be able to, but probably not the way you think. The latter restriction is going to be relaxed in C++20 as a result of P0784 to something like:

  • a new-expression (8.3.4), unless the selected allocation function is a replaceable global allocation function (21.6.2.1, 21.6.2.2);

That is, new T will become fine but new (ptr) T will still not be allowed. As part of making std::vector constexpr-friendly, we need to be able to manage "raw" memory - but we still can't actually manage truly raw memory. Everything still has to be typed. Dealing with raw bytes is not going to work.

But std::allocator doesn't entirely deal in raw bytes. allocate(n) gives you a T* and construct takes a T* as a location and a bunch of arguments and creates a new object at that location. You may be wondering at this point how this is any different from placement new - and the only difference is that sticking with std::allocator, we stay in the land of T* - but placement new uses void*. That distinction turns out to be critical.

Unfortunately, this has the interesting consequence of your constexpr version "allocates" memory (but it allocates compiler memory, which will get elevated to static storage as necessary - so this does what you want) - but your pure runtime version surely does not want to allocate memory, indeed the whole point would be that it does not. To that end, you will have to use is_constant_evaluated() to switch between the allocating at constant evaluation time and non-allocating at runtime. This is admittedly not beautiful, but it should work.

if constexpr and C4702 (and C4100, and C4715)

It's unreachable because for a given expansion of the template based on the template arguments the function will only ever pass the condition and return true or fail and return false. There is no case where it could go either way for the same type. It's essentially expanding to

if (true) {
return true;
}
return false; // Obviously will never happen

I'd rewrite it to only have a single return statement.

template <typename T, typename VariantType>
inline bool MatchMonostate( VariantType& variant )
{
SUPPRESS_C4100( variant );
bool retval = false;
if constexpr ( std::is_same_v<T, std::monostate> )
{
variant = std::monostate();
retval = true;
}
return retval;
}

Also, in the case where the condition is true variant is not unused. You may want to move that line that suppresses the warning (which basically turns into (void)variant) to an else statement.



Related Topics



Leave a reply



Submit