6

Consider the following example code :

#include <tuple>
void blah();
int buh;

constexpr auto get() 
{ 
    return std::get<0>(std::make_tuple(&blah, &buh));
}

int main()
{
    get();
}

One would expect that since the function get() is a constant-expression, it would return a constant.

That's not what happens: std::make_tuple, std::get are instantiated and called: https://godbolt.org/g/PkHrTp

Now, if we replace the implementation of get() by

constexpr auto get() 
{ 
    constexpr auto x = std::get<0>(std::make_tuple(&blah, &buh));
    return x;
}

We get the expected behaviour : the parameter x's computation is optimized out, even at -O0, and make_tuple, get are not instantiated, which can be fairly useful to reduce binary bloat.

Is there an idiomatic way to enforce that functions of the form constexpr auto foo() always behave like in the second example ?

For now I would resort to :

#define constexpr_return(X) do { constexpr auto constexpr_return_x_ = X; return constexpr_return_x_; } while(0)

constexpr_return(std::get<0>(std::make_tuple(&blah, &buh)));

for instance but I don't know if this is optimal.

Jean-Michaël Celerier
  • 7,412
  • 3
  • 54
  • 75
  • 1
    I recommend perhaps not asking directly for "another library". While asking for a library recommendation can be considered off-topic, this question seems very valuable without that one part. – Drew Dormann May 24 '18 at 21:37
  • 3
    It's worth pointing out that even at -O1, everything disappears. I don't expect that your concern turns out to be a problem very often. When it is, that piece of code can be reworked. – chris May 24 '18 at 21:43
  • Add `-O3` to the compilation options. – Richard Critten May 24 '18 at 21:43
  • 2
    Looking at unoptimized assembly really isn't helpful. Even at -O1 all the code is optimized away: https://godbolt.org/g/ExKyML – NathanOliver May 24 '18 at 21:52
  • I know, I know, but my debug binaries are > 1gb in size (for less than 10 mb at -O3). That's the part I'm trying to improve. – Jean-Michaël Celerier May 24 '18 at 21:54
  • Your "solution" only works for function without parameters, How about `constexpr int identity(int x) {return x;}` ? – Jarod42 May 24 '18 at 23:27
  • 1
    BTW, `constexpr auto a = get();` would even remove the call to `get()` (both version). – Jarod42 May 24 '18 at 23:44
  • A `constexpr` function can always be called in a non-`constexpr` context. I believe the only way to force it to be executed as a `constexpr` is to call it in a context that is unambiguously `constexpr` such as you did in the second example by initializing a `constexpr`. Another way would be to assign the result of `get` to a `constexpr`. In the first example, you never use `get` at compile time. – François Andrieux May 25 '18 at 02:09
  • Jarod42: I don't think it could work with function that have parameters; `identity((int)getch())` for instance. But constexpr functions that don't have parameter are *always* constant expressions, aren't they ? – Jean-Michaël Celerier May 25 '18 at 06:20
  • Possible duplicate of [Force constexpr to be evaluated at compile time](https://stackoverflow.com/questions/24322386/force-constexpr-to-be-evaluated-at-compile-time) – Arne Vogel May 25 '18 at 11:27
  • What's funny is if you wrap the calls to `get` within `static_assert`, they get optimized out – AndyG May 25 '18 at 14:55

1 Answers1

2
template<auto x>
std::integral_constant< std::decay_t<decltype(x)>, x > k{};

non-type template parameters must practically be evaluated at compile time.

k<get()> or in certain corner cases k<get()>() probably does what you want.

This doesn't work for constexpr values that cannot be passed as non-type template parameters, but it does work for integers and pointers and function pointers.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524