Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does if constexpr require an else to work?

I am trying to use if constexpr in the following way:

template<template <typename First, typename Second> class Trait,
    typename First, typename Second, typename... Rest>
constexpr bool binaryTraitAre_impl()
{
    if constexpr (sizeof... (Rest) == 0)
    {
        return Trait<First, Second>{}();    
    }
    return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
}

Example use case:

static_assert(binaryTraitAre_impl<std::is_convertible,
    int, int&,
    int*, void*>());

But this fails to compile

clang:

error: no matching function for call to 'binaryTraitAre_impl'
        return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
                                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

gcc:

prog.cc: In instantiation of 'constexpr bool binaryTraitAre_impl() [with Trait = std::is_convertible; First = int*; Second = void*; Rest = {}]':
prog.cc:9:80:   required from 'constexpr bool binaryTraitAre_impl() [with Trait = std::is_convertible; First = int; Second = int&; Rest = {int*, void*}]'
prog.cc:15:83:   required from here
prog.cc:9:80: error: no matching function for call to 'binaryTraitAre_impl<template<class _From, class _To> struct std::is_convertible>()'
    9 |         return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
      |                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
prog.cc:3:17: note: candidate: 'template<template<class First, class Second> class Trait, class First, class Second, class ... Rest> constexpr bool binaryTraitAre_impl()'
    3 |  constexpr bool binaryTraitAre_impl()
      |                 ^~~~~~~~~~~~~~~~~~~
prog.cc:3:17: note:   template argument deduction/substitution failed:
prog.cc:9:80: note:   couldn't deduce template parameter 'First'
    9 |         return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
      |                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~

But I found the error goes away once I add else:

template<template <typename First, typename Second> class Trait,
    typename First, typename Second, typename... Rest>
constexpr bool binaryTraitAre_impl()
{
    if constexpr (sizeof... (Rest) == 0)
    {
        return Trait<First, Second>{}();
    }
    else
    {
        return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
    }
}

live demo

What happened? Why can the compiler not infer the else in this case?

like image 920
Chen Li Avatar asked Dec 17 '18 15:12

Chen Li


People also ask

What does if constexpr do?

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

Should I use constexpr everywhere?

Yes. I believe putting such const ness is always a good practice wherever you can. For example in your class if a given method is not modifying any member then you always tend to put a const keyword in the end.

What is if constexpr in C++?

This is a big one! The static-if for C++! The feature allows you to discard branches of an if statement at compile-time based on a constant expression condition.

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.

Do I need a second constexpr?

As such, you can clearly see that the second constexpr is needed. 1: "block" is not the correct terminology. if is a statement (with optional else part). A block is { /*...*/ }. Thanks for contributing an answer to Stack Overflow!

Do we need to put constexpr after every IF-ELSE block?

Do we need to put constexpr after every if statement in if-else block in these kind of situations? Yes. The else-if block 1 is a lie :), there are only if blocks 1 and else blocks 1.

What are the advantages of Using constexpr functions?

constexpr functions can easily be converted into regular functions as requirements change. constexpr functions compile much quicker than the equivalent template-based solutions, which scale linearly with the depth of the template-recursion. As an example, take a look at these two compile-time implementations of the Fibonacci sequence:

What is if-constexpr and how do I use it?

Before diving into if-constexpr, it might be useful to have a quick recap of constexpr. Introduced in C++ 11, constexpr is a keyword that marks an expression or function as having a compile-time constant result. OK, so you might be wondering what the purpose of constexpr is.


2 Answers

if constexpr when the clause is true doesn't eliminate code outside of the corresponding else block.

You could extend C++ to do that, but it quickly becomes a pain. Only the most trivial cases are obvious, and specifying what the trivial cases are is a pain. I mean do you cover:

if constexpr( blah ){
  if (true) return 7;
}

? How about

if constexpr( blah ){
  if (blah) return 7;
  else exit(-1);
}

? Or

if constexpr( blah ){
  if (blah) return 7;
  else return false;
}

or

if constexpr( blah ){
  if (blah) goto foo;
  return false;
foo: return true;
}

or how about:

if constexpr( blah ){
  std::size_t count = 0;
  while (foo != 1 && (++count < (std::size_t)-1))
    switch (foo%2) {
      case 1: foo = 3*foo+1;
      case 0: foo = foo/2;
    }
  }
  if (count < (std::size_t)-1) return true;
}

? I can come up with a near continuum of cases that are slightly more or less "obvious" in their never-return. And the payoff? Not having an else. Lots of problems, little benefit.


Compilers have ad-hoc rules to detect unreachable code and the like. These don't have to be as formally specified as the standard, and they can differ from one compiler to another.

The standard, meanwhile, has to be the same for every compiler. And the rules for what is and isn't eliminated have to be identical.

The standard applies a simple rule; the if and else blocks are the only candidates for eliminatation.


So the standard doesn't do that. If you want code to be eliminated, put it in a if constexpr or else block of the if constexpr. Language development resources are better spent on things that have better yield and are less painful.

like image 157
Yakk - Adam Nevraumont Avatar answered Sep 29 '22 14:09

Yakk - Adam Nevraumont


This is the excerpt from cppreference on constexpr if:

Constexpr If The statement that begins with if constexpr is known as the constexpr if statement.

In a constexpr if statement, the value of condition must be a contextually converted constant expression of type bool. If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.

It is clear that only one of the two branches is discarded. In your case, the culprit code cannot be discarded because it's outside the else clause.

like image 39
Matthieu Brucher Avatar answered Sep 29 '22 16:09

Matthieu Brucher