6

I'm trying to change the value of a constexpr object member via a method but i don't understand why it's not working in this specific case :

#include <iostream>

struct test
{
    int m_counter = 0;

    constexpr test()
    {
        m_counter++;
        m_counter++;
        increment();
        increment();
        increment();
    }

    constexpr void increment()
    {
        m_counter++;   
    }

    constexpr int value() const
    {
        return m_counter;
    }
};

template<int value>
constexpr void check()
{
    std::cout << value << std::endl;
}

// constexpr test t; // value = 3, why ?

int main()
{
    constexpr test t; // value = 5, ok
    check<t.value()>();
}

I don't understand why value is 3 when i create object in global scope. msvc and clang display 5 in both cases but not gcc. Who is wrong?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
ads00
  • 61
  • 1
  • 4
  • 1
    [Coliru showing different behavior between gcc and clang](http://coliru.stacked-crooked.com/a/24be6d04e42ddc4c) – Vittorio Romeo Mar 14 '17 at 10:29
  • Also, which version of GCC are you using? What flags are you passing to GCC? Which version of Clang, and what flags are you passing to it? Same with MSVC++? Most importantly we need to know if you use C++11 or C++14? `constexpr` constructors and functions behave differently in the two standards (see e.g. [this `constexpr` reference](http://en.cppreference.com/w/cpp/language/constexpr) for more information). – Some programmer dude Mar 14 '17 at 10:30
  • 2
    ...it looks like a possible gcc bug to me – Vittorio Romeo Mar 14 '17 at 10:30
  • `check` being `constexpr` is ill-formed (NDR); anything involving standard streams can never be a constant expression, regardless of the template argument to `check`. – ildjarn Mar 14 '17 at 10:44
  • @ildjarn: is the compiler required to produce an error by the Standard in that case, or is it OK to simply ignore the `constexpr` as it's happening currently? – Vittorio Romeo Mar 14 '17 at 10:46
  • @VittorioRomeo : NDR is 'no diagnostic required', which includes compiler warnings/errors; it's UB. – ildjarn Mar 14 '17 at 10:48
  • 1
    Reported as bug [#80039](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80039) – Vittorio Romeo Mar 14 '17 at 10:52
  • I use C++14 : GCC 6.3 g++ -std=c++14 -Wall CLANG 3.9.1 clang++ -std=c++14 MSVC 2015 /std:c++14 – ads00 Mar 14 '17 at 10:53
  • Isn't `increment()` itself ill-formed? – Barry Mar 14 '17 at 13:34
  • @Barry: why do you think it is? – Vittorio Romeo Mar 14 '17 at 16:52
  • @VittorioRomeo Because it's modifying a non-static data member. – Barry Mar 14 '17 at 16:57
  • @Barry: that's not something [which is forbidden](http://en.cppreference.com/w/cpp/language/constexpr) in C++14 `constexpr` function bodies – Vittorio Romeo Mar 14 '17 at 16:58
  • @VittorioRomeo But in the context of initializing a `constexpr` object, it violates http://eel.is/c++draft/expr.const#2.16 – Barry Mar 14 '17 at 16:59
  • @Barry: that does indeed look like a potential violation, but I'm not confident about it. If that's the case, it should be added to cppreference's "`constexpr` constructor body restriction list" . – Vittorio Romeo Mar 14 '17 at 17:06
  • @Barry: are http://stackoverflow.com/questions/32699007/ and http://stackoverflow.com/questions/23647492 therefore wrong? Or is the violation you're describing happening because `increment()` is being called in `test`'s constructor? – Vittorio Romeo Mar 14 '17 at 17:14
  • @VittorioRomeo Sorry, what I meant was, the function itself is fine - it's not a violation of the restriction of constexpr member functions. But invoking it, on a constexpr object, shouldn't be. – Barry Mar 14 '17 at 17:15

1 Answers1

5

This seems to be a g++ bug, reproducible from g++ 5.1 up to g++ 7.0. I think it is a bug because the behavior seems nonsensical. I played around with the snippet and I believe that the compiler only executes the first increment() call if the variable is global, disregarding the other invocations:

constexpr test()
{
    m_counter++; // executed
    m_counter++; // executed
    increment(); // executed
    increment(); // NOT executed
    increment(); // NOT executed
}

// [...] will print 3

constexpr test()
{
    m_counter++; // executed
    m_counter++; // executed
    increment(); // executed
}

// [...] will print 3

constexpr test()
{
    m_counter++; // executed
    m_counter++; // executed
}

// [...] will print 2

constexpr test()
{
    m_counter++; // executed
    m_counter++; // executed
    increment(); // executed
    increment(); // NOT executed
    increment(); // NOT executed
    increment(); // NOT executed
    increment(); // NOT executed
    increment(); // NOT executed
}

// [...] will print 3

Basically, only one increment() inside the constructor will be executed. Adding more increment() calls has no effect. Adding more m_counter++ instructions will however work properly.

constexpr test()
{
    m_counter++; // executed 
    m_counter++; // executed
    increment(); // executed
    increment(); // NOT executed
    m_counter++; // executed
    m_counter++; // executed
    increment(); // NOT executed
    increment(); // NOT executed
}

// [...] will print 5

I reported this on the g++ bug tracker as as bug #80039.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416