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?
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.
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.
Related
I am trying to understand the utility of if constexpr and want to know if there is any utility in using it in this way.
template<bool B>
int fun()
{
if constexpr (B)
return 1;
return 0;
}
Is this function changed at all by using if constexpr instead of a regular if? I assume the performance would be the same. My understanding of templates is that the outcome of the if statement is already known at compile time so there is no difference.
Utility of constexpr?
A trivial example... if you write the following function
template <typename T>
auto foo (T const & val)
{
if ( true == std::is_same_v<T, std::string>> )
return val.size()
else
return val;
}
and call it with an integer
foo(42);
you get a compilation error, because the instruction
val.size();
has to be instantiated also when val is an int but, unfortunately, int isn't a class with a size() method
But if you add constexpr after the if
// VVVVVVVVV
if constexpr ( true == std::is_same_v<T, std::string>> )
return val.size()
now the return val.size(); instruction is instantiated only when T is std::string, so you can call foo() also with arguments without a size() method.
---- EDIT ----
As #prapin observed in a comment (thanks!), if constexpr can be necessary for an auto function.
I propose another trivial (and silly) example
Without if constexpr, the following bar() function
template <typename T>
auto bar (T const &)
{
if ( true == std::is_same_v<T, std::string>> )
return 42;
else
return 42L;
}
doesn't compile, because the first return return a int value, the second return a long; so, given that without if constexpr the compiler must instantiate both return's, so the compiler can't conciliate the returns types and can't determine the return type of the function.
With if constexpr,
if constexpr ( true == std::is_same_v<T, std::string>> )
return 42;
else
return 42L;
the compiler instantiate the first return or the second one; never both. So the compiler ever determine the type returned from the function (int when called with a std::string, long otherwise).
I was messing around with c++20 consteval in GCC 10 and wrote this code
#include <optional>
#include <tuple>
#include <iostream>
template <std::size_t N, typename Predicate, typename Tuple>
consteval std::optional<std::size_t> find_if_impl(Predicate&& pred,
Tuple&& t) noexcept {
constexpr std::size_t I = std::tuple_size_v<std::decay_t<decltype(t)>> - N;
if constexpr (N == 0u) {
return std::nullopt;
} else {
return pred(std::get<I>(t))
? std::make_optional(I)
: find_if_impl<N - 1u>(std::forward<decltype(pred)>(pred),
std::forward<decltype(t)>(t));
}
}
template <typename Predicate, typename Tuple>
consteval std::optional<std::size_t> find_if(Predicate&& pred,
Tuple&& t) noexcept {
return find_if_impl<std::tuple_size_v<std::decay_t<decltype(t)>>>(
std::forward<decltype(pred)>(pred), std::forward<decltype(t)>(t));
}
constexpr auto is_integral = [](auto&& x) noexcept {
return std::is_integral_v<std::decay_t<decltype(x)>>;
};
int main() {
auto t0 = std::make_tuple(9, 1.f, 2.f);
constexpr auto i = find_if(is_integral, t0);
if constexpr(i.has_value()) {
std::cout << std::get<i.value()>(t0) << std::endl;
}
}
Which is supposed to work like the STL find algorithm but on tuples and instead of returning an iterator, it returns an optional index based on a compile time predicate. Now this code compiles just fine and it prints out
9
But if the tuple does not contain an element that's an integral type, the program doesn't compile, because the i.value() is still called on an empty optional. Now why is that?
This is just how constexpr if works. If we check [stmt.if]/2
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 ([temp.pre]), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.[...]
emphasis mine
So we can see that we only do not evaluate the discarded expression if we are in a template and if the condition is value-dependent. main is not a function template so the body of the if statement is still checked by the compiler for correctness.
Cppreference also says this in their section about constexpr if with:
If a constexpr if statement appears inside a templated entity, and if condition is not value-dependent after instantiation, the discarded statement is not instantiated when the enclosing template is instantiated .
template<typename T, typename ... Rest>
void g(T&& p, Rest&& ...rs) {
// ... handle p
if constexpr (sizeof...(rs) > 0)
g(rs...); // never instantiated with an empty argument list.
}
Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive:
void f() {
if constexpr(false) {
int i = 0;
int *p = i; // Error even though in discarded statement
}
}
So I wrote an answer here: https://stackoverflow.com/a/56569397/2642059 which strives to compute log2 at compile time like so:
template <unsigned int x>
constexpr enable_if_t<x != 0U, int> log2 = 1 + log2<x / 2U>;
template <>
constexpr int log2<1U> = 0;
This works fine but I didn't feel like I should have had to specialize:
template <unsigned int x>
constexpr enable_if_t<x != 0U, int> log2 = x < 4U ? 1 : 1 + log2<x / 2U>;
But this gives me the error:
In substitution of template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = (0u != 0u); _Tp = int]:
prog.cpp:7:61: recursively required from constexpr std::enable_if_t<true, int> log2<4u>
prog.cpp:7:61: required from constexpr std::enable_if_t<true, int> log2<8u>
prog.cpp:10:11: required from here
/usr/include/c++/6/type_traits:2523:61: error: no type named type in struct std::enable_if<false, int>
Is there a way I can prevent the compiler from unrolling the recursion too far?
You use recursion to calculate log2. Each and every recursive operation in our life needs the leaf case.
In case of recursive leaf functions, the leaf case can be provided with non-recursive returns. However, with template variables the only way to provide the leaf case would be with specialization, there is no other way at all.
I believe, that you can achieve the very same goals with constexpr function and no TMP:
#include <type_traits>
constexpr int log2(int arg) {
if (arg == 0) return 0;
if (arg == 1) return 0;
return 1 + log2(arg / 2u);
}
constexpr std::integral_constant<int, log2(16)> z; // z.value == 4
This works with both run-time and compile-time arguments and generally should be preferred over pure TMP solution, except for educational purposes.
For educational or other undisclosed purposes, you can use exclusive compile-time like that:
#include <type_traits>
template<int arg>
constexpr int log2(std::integral_constant<int, arg> ) {
static_assert(arg > 0, "Bad arg to log2!");
if constexpr (arg == 1) {
return 0;
} else {
return 1 + log2(std::integral_constant<int, arg / 2> {});
}
}
int k = log2(std::integral_constant<int, 16>{});
I was trying out a way to specialize member function based on a class template parameter, without having to use SFINAE on the class (and resulting in either code duplication or creation of another class).
Since two template parameters can't be optional, and argument enable_if is frowned upon in guidelines, so I tried out the (remaining) following 2 ways:
template <bool boolean = true>
struct sample {
constexpr typename std::enable_if<boolean, int>::type bool_check_return(
int s) const noexcept {
return s + 1;
}
constexpr typename std::enable_if<!boolean, int>::type bool_check_return(
int s) const noexcept {
return s;
}
template <typename std::enable_if<boolean, int>::type = 0>
int bool_check_template(
int s) const noexcept {
return s + 1;
}
template <typename std::enable_if<!boolean, int>::type = 0>
int bool_check_template(
int s) const noexcept {
return s;
}
};
Godbolt link
On first look, it doesn't seem obvious to me why the return type SFINAE gives the following error about "overloading is not applicable to functions differing only in return type". SFINAE should have ensured only one copy, not two.
Which part of standard am I unknowingly violating? Or is this a compiler bug? Practically, this would not be an issue in C++17 with if constexpr (and since one form works, I can simply choose that one).
This error is present in C++11 through C++17, which gives a low probability for the compilers to be wrong about this.
error: functions that differ only in their return type cannot be overloaded
constexpr typename std::enable_if<!boolean, int>::type bool_check_return(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
note: previous definition is here
constexpr typename std::enable_if<boolean, int>::type bool_check_return(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
1 error generated.
There is no SFINAE involved here because bool_check_return are not templates themselves. They are just regular overloaded functions that differ only in return type. Making them templates would solve the problem by allowing only one of them:
template<bool enabled = boolean>
constexpr typename std::enable_if<enabled, int>::type bool_check_return(
int s) const noexcept {
return s + 1;
}
template<bool enabled = boolean>
constexpr typename std::enable_if<not enabled, int>::type bool_check_return(
int s) const noexcept {
return s;
}
The compiler is right. In any case, you cannot overload two functions with the same return type, even though only one return type will be valid after substitution.
From [over.load]:
Certain function declarations cannot be overloaded:
Function declarations that differ only in the return type, the exception specification, or both cannot be overloaded.
...
There is no exception to this rule.
Is there a way to fix the following problem:
This code produces a C4702 warning 'unreachable code' (on VC++ 15.8 with /std:c++17)
template <typename T, typename VariantType>
inline bool MatchMonostate( VariantType& variant )
{
SUPPRESS_C4100( variant );
if constexpr ( std::is_same_v<T, std::monostate> )
{
variant = std::monostate();
return true;
}
return false; // !!! unreachable if the above is true !!! => C4702
}
to suppress the C4100 'unreferenced formal parameter' warning, I'm already using the trick
#define SUPPRESS_C4100(x) ((void)x)
The simple idea of adding
else
{
return false;
}
results in warning C4715 'not all control paths return a value' instead.
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.
As the direct answer to the direct question. On the subject of if constexpr. Consider this:
template <typename T, typename ... params >
inline bool match_monostate
(std::variant<params ...> & variant) noexcept
{
if constexpr (std::is_same_v<T, std::monostate>)
{
variant = std::monostate{} ;
// compiles only if called with variant
// whose one alternative is std::monostate
return true;
}
else {
return false;
}
}
Depending on the bool result of the if constexpr expression, compiler actually produces two functions. This version is produced when if constexpr() yields true:
template <typename T, typename ... params >
inline bool
match_monostate (std::variant<params ...> & variant) noexcept
{
variant = std::monostate{} ;
// compiles only if called with variant
// whose one alternative is std::monostate
return true;
}
This version is produced when if constexpr() yields false:
template <typename T, typename ... params >
inline bool
match_monostate (std::variant<params ...> & variant) noexcept
{
return false;
}
The second version might emit warnings about unused argument. But (it seems) not if using the latest versions of clang/gcc/msvc. For older compilers as "old123987" also pointed out one can add the standard attribute to the signature. Like so:
template <typename T, typename ... params >
inline bool
match_monostate ([[maybe_unused]] std::variant<params ...> & variant) ;
That will stop the warning emitting.