39

Is there a method to decide whether something can be constexpr evaluated, and use the result as a constexpr boolean? My simplified use case is as follows:

template <typename base>
class derived
{
    template<size_t size>
    void do_stuff() { (...) }

    void do_stuff(size_t size) { (...) }
public:
    void execute()
    {
        if constexpr(is_constexpr(base::get_data())
        {
            do_stuff<base::get_data()>();
        }
        else
        {
            do_stuff(base::get_data());
        }
    }
}

My target is C++2a.

I found the following reddit thread, but I'm not a big fan of the macros. https://www.reddit.com/r/cpp/comments/7c208c/is_constexpr_a_macro_that_check_if_an_expression/

max66
  • 65,235
  • 10
  • 71
  • 111
Aart Stuurman
  • 3,188
  • 4
  • 26
  • 44
  • 1
    Hmm, the body of a `if constexpr` will only be evaluated if the expression in the `if constexpr` is true at compile time. Is that what you are looking for? – Jesper Juhl Mar 21 '19 at 20:13
  • 1
    But what if the test in the if constexpr([test]) is not evaluatable at compile time? – Aart Stuurman Mar 21 '19 at 20:17
  • 10
    Maybe you can do something with [`std::is_constant_evaluated`](https://en.cppreference.com/w/cpp/types/is_constant_evaluated)? – 0x5453 Mar 21 '19 at 20:18
  • https://en.cppreference.com/w/cpp/language/if – Jesper Juhl Mar 21 '19 at 20:18
  • If it's not evaluateable at compile time then what would you have the compiler do except error out or evaluate at run time? – Jesper Juhl Mar 21 '19 at 20:22
  • If it can be evaluated at compile time, the first body should be executed. Else the second runtime body should be executed. I'm currently investigating 0x5453 's recommendation. – Aart Stuurman Mar 21 '19 at 20:24
  • 3
    @AartStuurman: What is `do_stuff` that it can run at compile time or runtime, but itself should not be `constexpr`? Wouldn't it make more sense to just make it a `constexpr` function, and pass it the value of `get_data` as a parameter? – Nicol Bolas Mar 21 '19 at 20:38
  • Meanwhile I've revised my design choice indeed, but I think this an interesting question nonetheless :) – Aart Stuurman Mar 21 '19 at 22:58

3 Answers3

45

Here's another solution, which is more generic (applicable to any expression, without defining a separate template each time).

This solution leverages that (1) lambda expressions can be constexpr as of C++17 (2) the type of a captureless lambda is default constructible as of C++20.

The idea is, the overload that returns true is selected when and only when Lambda{}() can appear within a template argument, which effectively requires the lambda invocation to be a constant expression.

template<class Lambda, int=(Lambda{}(), 0)>
constexpr bool is_constexpr(Lambda) { return true; }
constexpr bool is_constexpr(...) { return false; }

template <typename base>
class derived
{
    // ...

    void execute()
    {
        if constexpr(is_constexpr([]{ base::get_data(); }))
            do_stuff<base::get_data()>();
        else
            do_stuff(base::get_data());
    }
}
cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • Intriguing solution... this way you get the same result of my custom type traits but more synthetically and, above all, the exact expression verified (`base::get_data()`) is embedded in the argument and not hard-coded as in my solution. Very nice. I have to remember it. – max66 Mar 22 '19 at 00:24
  • I am accepting this, because it is an answer to the a generic case of the question. max66 answer is also very useful(in non-c++2a cases), but requires repetition for every usage :) – Aart Stuurman Mar 22 '19 at 09:42
  • 4
    Comma operator SFINAE... my mind goes BOOM. – organicoman Oct 24 '21 at 08:02
  • An enlightening solution to the problem. I have been doing some testing on this, and I believe that the use of the comma SFINAE is unneeded and I am pretty sure that a template of the form `template`, where my `T` is your `Lambda` would equally suffice. – user2628206 Jan 17 '22 at 07:17
  • On second thoughts, the comma SFINE is needed due to void return type of lambda. (in my testing I was using a lambda akin to `[]{ base::get_data(); return true;}`, which is always non-void. – user2628206 Jan 17 '22 at 19:24
16

Not exactly what you asked (I've developer a custom type trait specific for a get_value() static method... maybe it's possible to generalize it but, at the moment, I don't know how) but I suppose you can use SFINAE and make something as follows

#include <iostream>
#include <type_traits>

template <typename T>
constexpr auto icee_helper (int)
   -> decltype( std::integral_constant<decltype(T::get_data()), T::get_data()>{},
                std::true_type{} );

template <typename>
constexpr auto icee_helper (long)
   -> std::false_type;

template <typename T>
using isConstExprEval = decltype(icee_helper<T>(0));

template <typename base>
struct derived
 {
   template <std::size_t I>
   void do_stuff()
    { std::cout << "constexpr case (" << I << ')' << std::endl; }

   void do_stuff (std::size_t i)
    { std::cout << "not constexpr case (" << i << ')' << std::endl; }

   void execute ()
    {
      if constexpr ( isConstExprEval<base>::value )
         do_stuff<base::get_data()>();
      else
         do_stuff(base::get_data());
    }
 };

struct foo
 { static constexpr std::size_t get_data () { return 1u; } };

struct bar
 { static std::size_t get_data () { return 2u; } };

int main ()
 { 
   derived<foo>{}.execute(); // print "constexpr case (1)"
   derived<bar>{}.execute(); // print "not constexpr case (2)"
 }
max66
  • 65,235
  • 10
  • 71
  • 111
11
template<auto> struct require_constant;
template<class T>
concept has_constexpr_data = requires { typename require_constant<T::get_data()>; };

This is basically what's used by std::ranges::split_view.

cpplearner
  • 13,776
  • 2
  • 47
  • 72