12

I'm looking to use some c++11 features in some existing c++ projects, so I started changing compile flags in Clang for some projects, and I keep running into a specific issue regarding C++11's treatment of conversion operators (or cast operators) that I didn't expect to see and don't understand why this is now considered an error when it's been valid C++ code that's not c++11

I've boiled it down to this simple example:

#include <iostream>
#include <vector>

class SerializableFormat
{
public:
    size_t i;
};

class A
{
public:
    size_t x, y;

    A(size_t n) : x(n), y(1) { }

    operator const SerializableFormat() const
    {
        SerializableFormat result;

        result.i = x;

        if (y)
        {
            result.i /= y;
        }

        return result;
    }
};

int main(int argc, const char * argv[])
{
    std::vector<SerializableFormat> v;

    for(size_t i = 0; i < 20; i++)
    {
        v.push_back(A(i));
    }

    return 0;
}
  • If Clang's compilation flags are set to -std=c++98 and libstdc++, there are no issues and this compiles fine.
  • If Clang's compilation flags are set to -std=c++11 and libc++, I get the error No viable conversion from 'A' to 'value_type' (aka 'SerializableFormat')

Just to make it clear-- in case you're thinking about giving SerializableFormat a constructor just for the class A:

Since the SerializableFormat class is more suited for conversion to and from various classes, it makes sense for A (and other classes that wish to be serializable) to have constructors and conversion operators rather than expect SerializableFormat to cover every type of class that wants to be serializable, so modifying SerializableFormat to have a special constructor is not a solution.

Can anyone see what I'm doing wrong here?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
tjgrant
  • 438
  • 4
  • 18
  • 4
    With GCC, libstdc++, and `-std=c++11`, it still compiles. There is no point in your conversion operator returning `const SerializableFormat` -- just return `SerializableFormat` instead, and it'll also work with clang -- but I don't have an answer as to whether your code is valid. –  Feb 13 '15 at 22:54
  • I didn't notice the LLVM tag until just now. That changes things. – Austin Mullins Feb 13 '15 at 23:00
  • 2
    Minimal testcase: `struct A { }; struct B { operator const A(); }; void f() { (A(B())); }` gets accepted by GCC in all modes, by clang only in C++03 mode. –  Feb 13 '15 at 23:04
  • related: http://stackoverflow.com/q/16834937/819272 – TemplateRex Feb 16 '15 at 21:20

1 Answers1

13

As the comments correctly note, you can get compiling by dropping the const in the return type of your SerializableFormat conversion operator:

operator const SerializableFormat() const

As to whether clang is correct in this behavior is a matter of some dispute. The issue is being tracked by clang bug report 16682. At this time there is talk of creating a CWG (C++ committee) issue report, but that has not yet been done. I note that this bug report has been open for some time now (2013-07-23), but was updated as recently as 2015-01-28.

In the meantime, practical advice is just never to return by const-value. This was decent advice for C++98/03, but with move semantics becomes bad advice because it will disable move semantics.

T.C.
  • 133,968
  • 17
  • 288
  • 421
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 2
    Why would you find it necessary to return a `const` object in the first place? You're constructing it on the spot, there's no need to protect it. If you were returning a *reference* that would be another matter. – Mark Ransom Feb 13 '15 at 23:54
  • 7
    @MarkRansom: In C++98/03 one guideline recommended always returning by `const` to prevent accidental assignment to an rvalue (the compiler prevents it if the rvalue is `const`). This guideline was immortalized in Scott Meyers Effective C++ Item 3. – Howard Hinnant Feb 13 '15 at 23:59