6

I wrote a function calculating the gcd of two numbers which uses std::swap in the case where the second parameter is greater than the first.

Some time later, I realised that std::swap is not constexpr, but my function still compiled and ran successfully.
I tried with MinGW-w64 8.1.0 and Visual C++ 2017 and it worked for both.

My first thought was that's because constexpr functions are allowed to be executed at runtime, so I tried std::integral_constant<int,gcd(32,12)>, and it worked.

However, I cannot use any of my own non-constexpr function (which is what I expect).

Here is my test code :

#include <utility>

inline void foo() noexcept {
}

template<typename T>
constexpr T gcd(T a, T b) {
    // foo();            // only works with non-constexpr j
    if(a<b) {
        std::swap(a, b); // works for both constexpr i and non-constexpr j
    }
    if(b==0) {
        return a;
    } else {
        return gcd(b, a%b);
    }
}

int main()
{
    constexpr int i = std::integral_constant<int, gcd(32, 12)>::value;
    int j = gcd(32,12);
}

So, my question is : why can I use std::swap in my function ?

Annyo
  • 1,387
  • 9
  • 20
  • 2
    `swap` is declared as `constexpr`. Take a look at [here](https://en.cppreference.com/w/cpp/algorithm/swap). – NutCracker Mar 11 '19 at 09:53
  • @NutCracker Since C++20, not C++17. I'm not surprised by gcc which muddies the standard boundaries, but VS is usually not lax. Except if you used latest instead of C++17. – Matthieu Brucher Mar 11 '19 at 09:53
  • https://stackoverflow.com/a/46373678/896012 – TonySalimi Mar 11 '19 at 09:55
  • Look at the header file where `std::swap` is defined. Perhaps it's marked as `constexpr` as a compiler-specific extension? – Some programmer dude Mar 11 '19 at 10:00
  • My opinion is that the compiler recognizes non-constexpr `swap` function as a function which is callable in a constant-expression because the argument passed is known during translation. That's why you can use `std::swap` in your function. Note that `constexpr` keyword is used only to express your intent. Check this [answer](https://stackoverflow.com/a/28821610/5517378). If this answers your question I would be glad to change this comment to an answer. – NutCracker Mar 11 '19 at 10:05
  • 1
    @NutCracker that's not how `constexpr` works. Compiler has no power to arbitrary make functions `constexpr` not declared as such. What we have here is an "extension-made-standard". – Dan M. Mar 11 '19 at 10:13
  • 5
    the `swap`-branch is never executed. try `gcd(12, 32)` (arguments flipped) and you get the compile error. – local-ninja Mar 11 '19 at 10:34
  • If you change the values passed in to `a < b` you get a [compile error](https://wandbox.org/permlink/Byuly2Pxhcm9b6Yk) with gcc. – super Mar 11 '19 at 10:35
  • @fdan Indeed, I failed to realise that. I'm surprised the compiler doesn't even warn me about this non-constexpr statement until it actually tries to execute it. I guess you can add this as an answer. – Annyo Mar 11 '19 at 10:52

1 Answers1

8

Here is a relevant quote from cppreference:

A constexpr function must satisfy the following requirements:

  • ...
  • there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression

There is a path that does not go through std::swap(), where a a>=b. In fact, for gcd(32, 12) the execution never goes through std::swap().

EDIT: I had a look at the C++14 draft. Section 7.1.5 The constexpr specifier. Paragraph 5 says:

For a non-template, non-defaulted constexpr function [...], if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.20), or, for a constructor, a constant initializer for some object (3.6.2), the program is ill-formed;

and the example they give is:

constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Michael Veksler
  • 8,217
  • 1
  • 20
  • 33