27

Given two programs where the only difference in the source code is the presence or absence of one constexpr, is it possible that the meaning of the program changes?

In other words, if there was a compiler option to ask the compiler to try really hard to infer constexpr where possible, would it break existing standard code and/or change its meaning in bad ways?

Imagine dealing with a codebase where the original developer forgot to include constexpr in places where it was possible, perhaps code written before C++11. It would be great if the compiler would infer constexpr to help you get on with your work. Of course, perhaps it should also warn about each time it does this inference, encouraging you to explicitly add the constexpr later. But it would still be useful. My worry is that it might break things?

So far, the only thing I can think of is that constexpr functions are implicitly inline and there can be situations where adding inline can change things in bad ways; for example if you break the one-definition-rule.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • Well, for example if compiler vendors choose to mark functions not marked in the standard as constexpr that could cause different behavior via SFINAE which is why it was this was eventually not allowed, see [Is it a conforming compiler extension to treat non-constexpr standard library functions as constexpr?](http://stackoverflow.com/q/27744079/1708801) – Shafik Yaghmour Sep 04 '15 at 11:36
  • Thanks for that @ShafikYaghmour. I had done some experiments with SFINAE to try to find divergence, but I couldn't. I guess my examples were too simplistic :) – Aaron McDaid Sep 04 '15 at 11:38
  • 2
    I have an [an example here where SFINAE breaks](http://stackoverflow.com/a/21319414/1708801) due to different treatments of undefined behavior in constant expressions. I still don't have an answer whether this is considered conforming or not. Not exactly the same but we can see how differing implementations can break SFINAE. – Shafik Yaghmour Sep 04 '15 at 11:40
  • A compiler that infers `constexpr` should, pretty much by definition, only do that for cases where it does not change behavior. – danielschemmel Sep 04 '15 at 11:48
  • The compiler can infer constexpr, and has been doing so for ages. It does constant folding as an optimization technique. It removes code based on constants, evaluates expressions etc. – Jens Sep 04 '15 at 11:52
  • I think floating point value may also differ (between constexpr and runtime value) if you use some additional flag as `-frounding-math`. – Jarod42 Sep 04 '15 at 11:53
  • 1
    @Jens yes but they have to obey the as-if rules. – Shafik Yaghmour Sep 04 '15 at 11:54
  • @ShafikYaghmour Yes. Did I imply that this is not the case? But as long as this is true, the compiler will do optimizations, e.g. eliminating if-branches, simplifying expressions etc. I was just relating to the point that a compiler should be able to infer constexpr, saying that this is being done for years. – Jens Sep 04 '15 at 12:06
  • @Jens you did not imply that, I just wanted to clarify. – Shafik Yaghmour Sep 04 '15 at 12:09
  • @Jens, perhaps `constexpr` is not exactly the same as "can return constant expressions". As you say, compilers have always been allowed to infer the latter, but not necessarily the former. If adding `constexpr` can change something, then this means (I think) that in some circumstances the compiler may infer the latter and not the former. – Aaron McDaid Sep 04 '15 at 12:09
  • @AaronMcDaid When I wrote the comment, I had more constexpr variables in mind, e.g. `constexpr double pi = 3.14`. This is what I think compilers are doing. For `constexpr` functions, it should also be possible, even when you do not consider the syntactic restrictions as a check. I think D does something similar for pure functions, and compilers do generate warnings like "member function could be const". – Jens Sep 04 '15 at 12:15

2 Answers2

14

There is an easy trick:

template<int n>struct i{};
int foo(int){return 0;}
constexpr int foo(char){return 'a';}

template<class T=int, T x=1,i<foo(x)>* =nullptr>
bool bar(){return true;}
template<class T=int, T x=1,class...Ts>
bool bar(Ts...){return false;}

if int foo(int) is constexpr, a different overload of bar is chosen by default.

With different code running, any behaviour change can occur.

live example (simply change which #define X is commented out).


Design of the example:

The char overload prevents the above code from being ill-formed, no diagnostic required, as all templates must have a valid specialization. foo<char> supplies that. In practice, its existence is not required: ADL could find a foo from far away, overloaded on a some_type*, then pass some_type* as T. Which means no compilation unit could prove the code was ill-formed.

The Ts... makes that bar overload less-preferred. So if the first one matches, there is no ambiguity. Only if the first one fails to match (due to a SFINAE caused by foo(x) not being constexpr) does the second overload get called (or if, say, someone passed arguments to it).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Perfect. I had been struggling to make something that demonstrated `constexpr`-dependent behaviour in both clang and g++. But this does the trick with both clang 3.5.0 and g++ 5.2.0 . – Aaron McDaid Sep 04 '15 at 13:17
  • .. I guess this shows that it can be possible to detect at compile-time whether a given function is `constexpr`. While this seems cool, it means that inferring `constexpr` would be dangerous as it would change the result of these tests. Finally, I don't really like this, now that I understand it. I think this should give an error - if the best overload isn't `constexpr`, then there should be an error if it's called in a context that requires a constant-expression. (In my humble opinion, just formed in the last few minutes) – Aaron McDaid Sep 04 '15 at 13:28
  • 2
    @AaronMcDaid The error is in the immediate context, so we get a substitution failure instead of an error. SFINAE means that almost any "signature" change (even existence!) can result in a different compile-time and run-time behavior in C++, and makes almost extensions to any interface impossible to make fully conforming. However, the cases where this happen are (in every case I've seen it, including above) *pathological*. – Yakk - Adam Nevraumont Sep 04 '15 at 13:32
  • I've just noticed that `foo(char)` does not need to be `constexpr` for this example to work. Both compiler still show behaviour that depends on whether `foo(int)` is constexpr. So that has changed my (limited) understanding as to how the answer worked. (to be continued...) – Aaron McDaid Sep 04 '15 at 14:00
  • ... So, the compiler starts with `foo(x)`, where `x` is an `int`. It prefers the `int` overload of `foo` over the `char` overload of `foo`. All well so far. And then, if the `int` overload is *not* `constexpr`, then SFINAE removes that overload of `bar`. Is this correct? In particular, it is not allowed to "save" the overload of `bar` by using the `char` overload of `foo` instead (even if it is `constexpr`)? – Aaron McDaid Sep 04 '15 at 14:01
  • @Aaron See my annendum: in practice, it need not even exist. Careful reading of the standard in a clause rarely applied demands that such an overload exist, but in practice no compiler cares. – Yakk - Adam Nevraumont Sep 04 '15 at 14:01
  • 1
    I am just using SFINAE to determine if `i` is a valid expression in the immediate context. The `char` overload is only required for an obscure technical demand of the standard (that all templates are able to be instantiated with some set of arguments). – Yakk - Adam Nevraumont Sep 04 '15 at 14:04
  • when I deleted the `char` overload clang behaved as expected, but g++ gave an error in the particular case where the `int` overload is not `constexpr`. I guess you're saying that both compilers are compliant with the standard here? – Aaron McDaid Sep 04 '15 at 14:04
  • Without said overload, and no other overloads of `foo` anywhere in the program, the above is an ill formed program with no diagnostic required. I doubt that is why gcc is generating an error, unless it generated the error after linking occurred. – Yakk - Adam Nevraumont Sep 04 '15 at 14:07
4

Given two programs where the only difference in the source code is the presence or absence of one constexpr, is it possible that the meaning of the program changes?

Yes, this is at least true for constexpr functions. It is the reason why implementations are not allowed to choose which standard functions are marked constexpr, the main issue is that users may observe different behaviors via SFINAE. This is documented in LWG issue 2013: Do library implementers have the freedom to add constexpr? which says (emphasis mine):

Some concern expressed when presented to full committee for the vote to WP status that this issue had been resolved without sufficient thought of the consequences for diverging library implementations, as users may use SFINAE to observe different behavior from otherwise identical code. Issue moved back to Review status, and will be discussed again in Portland with a larger group. Note for Portland: John Spicer has agreed to represent Core's concerns during any such discussion within LWG.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • 1
    @AaronMcDaid I am actually trying to make a live example, it has been a while since I looked at that code and I want to make sure I have the details are correct. – Shafik Yaghmour Sep 04 '15 at 11:59
  • It's true for `constexpr` functions in the specific. Apart from that, `constexpr` has other uses too, so I'd say it's partially true. – edmz Sep 04 '15 at 12:00
  • (I've just deleted an earlier comment I made on this answer, as I'm not yet able to use it to create an example where behaviour changes due to the presence of a `constexpr`) – Aaron McDaid Sep 04 '15 at 12:40
  • @AaronMcDaid here is [an example](http://melpon.org/wandbox/permlink/4sMJPdrZHAVwuRsg) with different behavior between clang and gcc where cos is marked constexpr in gcc but not clang. I am having trouble the undefined behavior example though. I never saved my shift example and reproducing it is a pain. – Shafik Yaghmour Sep 04 '15 at 12:48
  • @AaronMcDaid here [is the undefined behavior example](http://melpon.org/wandbox/permlink/V3y9SYeptTUREzrd) ... I have to think about this one to make sure I am not doing something wrong before I added it to my answer. – Shafik Yaghmour Sep 04 '15 at 12:59
  • 1
    Note to self, if you have a good example then include it in your answer, reproducing later on is never as trivial as you think it will be. – Shafik Yaghmour Sep 04 '15 at 13:00
  • Here's [an example](http://coliru.stacked-crooked.com/a/be7251682c8ce668) where adding `constexpr` changes the behaviour in clang 3.5.0. It compiles with, and without, `constexpr`, but the result changes because different overloads are selected. This is the kind of example I am looking for. Assuming clang is correct here (?), then it shows that compilers must be careful about inferring `constexpr` – Aaron McDaid Sep 04 '15 at 13:04