#include <cstdio>
#include <string>
class A {
std::string data;
public:
A() = default;
explicit A (const char* data) : data(data) {}
operator const char* () const;
explicit operator std::string() &&;
};
A::operator const char*() const {
printf("A -> const char*\n");
return data.c_str();
}
A::operator std::string() && {
printf("A -> std::string\n");
return data;
}
int main() {
A a("lorem");
std::string s(std::move(a));
printf("%s\n", s.c_str());
return 0;
}
The above code prints "A -> std::string" on gcc13 and "A -> const char*" on gcc14. Clang (18.1.8) does the same thing as gcc13. All compilers were called with -Wall -pedantic --std=c++17
The code is a minimal reproduction case.
I'm trying to add std::string
support to a custom class A
in a very old project. Unfortunately A
must have the implicit conversion to a c-str to avoid breaking existing code.
I had a working version but it's broken on gcc14.
What I want is for the move conversion operator to be called when a call site attempts to construct std::string
from an rvalue reference of A. All of c-cast, function-cast, static_cast and initialization worked on gcc13.
What I think is happening is that on gcc13, the move conversion is called, as expected, and only a move occurs. On gcc14, a two step implicit conversion of is taken, using the implicit A::operator const char*()
and std::string(const char*)
. This creates the second string using a copy instead of a move.
Naming the conversion operator like C c = std::move(a).operator std::string();
does call the desired conversion.
Is there a way to convince gcc14 of the desired behavior?
EDIT:
The std::string operator has to be explicit in my case. Existing code uses standard library functions that are overloaded for both c-str and std::string. Removing explicit would make these calls ambiguous.
I also tried to make a more generic reproduction case. This happens even if all the involved types are custom.
#include <cstdio>
class B {
};
class C;
class A {
B data;
public:
A() = default;
explicit A (B data) : data(data) {}
operator B () const;
explicit operator C() const;
};
class C {
B data;
public:
C(B data) : data(data) {
printf("C from B\n");
}
};
A::operator B() const {
printf("A -> B\n");
return data;
}
A::operator C() const {
printf("A -> C\n");
return C(data);
}
int main() {
A a(B{});
C s = static_cast<C>(a);
return 0;
}
With gcc13 and clang this prints
A -> C
C from B
and with gcc14 it prints
A -> B
C from B
explicit
fromexplicit operator std::string() &&;
? Demo – Preparativec-str
andstd::string
. – Faustina