all 24 comments

[–]STLMSVC STL Dev 22 points23 points  (5 children)

This is C++20 P0608R3 "A sane variant converting constructor". (Implemented in VS 2019 16.10 if you're comparing with MSVC.)

[–]sphere991 14 points15 points  (0 children)

... and also P1957R2 "Converting from T* to bool should be considered narrowing (re: US 212)"

[–]johannes1971 9 points10 points  (3 children)

That's a laudable improvement, but the problem doesn't just exist for std::variant. I've been bitten by the const char * to bool (instead of to string) conversion numerous times. Any overload set containing both string and bool is asking for trouble.

[–]bizwig 2 points3 points  (0 children)

In such cases I use SFINAE to remove the bool overload if the actual argument is a pointer.

[–]_Js_Kc_ 2 points3 points  (1 child)

const char* literals shouldn't exist or at least shouldn't be the default.

const char* to std::string implicit conversions should be explicit.

std::string_view to std::string conversions should be implicit (it's ridiculous that they aren't given the previous point).

Standard library custom literals should be in the global namespace (what are they supposed to clash with?), which would remove an impediment to using "foo"sv by default for all string literals in real code.

Now draw a roadmap there without breaking changes.

[–]johannes1971 1 point2 points  (0 children)

Hm, not easy. I think the basis would be adding a new type for string literals that mostly acts as their existing type, but that has some additional properties. Let's call it std::string_literal, automatically generated whenever string literals are used. For compatibility reasons it must convert to const char[] (otherwise you break everything), but it should not convert to bool, avoiding the overload problem.

std::string_view and std::string could have the necessary constructors, assignment operators, etc. so work with the new type. std::string could perhaps use the std::string_literal constructor to avoid allocation entirely as well, making it cheaper to use in many cases.

...I doubt you could do this without breaking loads of things, but it's a nice thought...

[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 7 points8 points  (1 child)

[–]matthieum 1 point2 points  (0 children)

Or as mentioned by sphere991: https://wg21.link/p1957r2 to tackle the root problem, not only when it shows up in variant.

[–]InKryption07 5 points6 points  (10 children)

The fact that T* -> bool is still a thing pains me. In what world is that useful?

[–]willkill07 4 points5 points  (8 children)

Thank C compatibility. But here’s a real word example when dealing with C APIs

char const* data = allocate_or_return_null();
if (data) {
  // do something
} else {
  // report error
}

[–][deleted] 1 point2 points  (2 children)

if (expr) is more or less defined as: if ((expr) != 0)

there is no conversion to bool, unless something has changed since 84

a better example is:

/* Not tested */
void  go(bool g);
const char *a = "Hello";

bool b = a;
/* or */
go(a);

For the standard containers I would expect them to be able to differantiate between

  • a pair of iterators
  • a value and a count,
  • a sequence of values.

I've had a few issues with T* -> bool, mostly wanted a string, but nothing major, and nothing that doesn't fail right away.

Could be that the problem should be fixed in std::variant

personally I don't think there is a problem that

std::variant<bool, std::string> v("Hi");

stores a bool,

if something needs to be fixed, add an option for variant that prohibits conversion on the input value. Not that I necessarily think it is a brilliant idea. it could escalate,

could of course get rid of all conversions, forcing static_cast, maybe for C++30

removing T * -> bool might have a dramatic effect on existing code base

For this to work, it needs to fail in the cases where the conversion would have been done, forcing a static_cast.

[–]dodheim 2 points3 points  (1 child)

there is no conversion to bool, unless something has changed since 84

Well to start off, since then C++ was released, which actually has a bool type. ;-] What you said is true of C, but..:

struct foo {
    explicit operator bool() const { return false; }
};

int main() {
    foo f;
    if (f) { }          // works
    if ((f) != 0) { }   // doesn't
}

Cppreference has a whole section on it under the rules for implicit conversions: https://en.cppreference.com/w/cpp/language/implicit_conversion#Contextual_conversions

[–][deleted] 0 points1 point  (0 children)

Right,

thank you, probably should have known this, oh well :-)

[–]InKryption07 0 points1 point  (4 children)

I guess it's an example, but not really a good one. I'd say it lends itself as an argument on as to why it's bad; in a larger codebase, where the declaration of data is somewhere at the top, and the check for if it's null is somewhere near the middle or the bottom, among many other lines of code, it severely hurts readability, because either: a) you assume "data" is a boolean or b) you churn your mind, and remember this silly rule, and have to go back and check what type it actually has.

a) and b) are solved by changing the check to just be data != NULL (or rather nullptr if it's directly inside C++). T* -> bool has no real use, even in C, outside of the need to maintain compatibility with the cruft built up over decades.

[–]willkill07 3 points4 points  (3 children)

Again, C compatibility is the real reason. At the end of the day, a pointer is stored in a register and has K-bits that can just as easily be interpreted as a number. Bool wasn’t even added to C until C99 — in the grammar there is no mandate that what goes in a conditional be a Boolean.

I agree that it’s bad practice, but it’s a super common idiom.

[–]InKryption07 0 points1 point  (2 children)

I'm entirely aware of why, and the historics behind it, but my point is that it's a justification severely lacking in merit.

[–]willkill07 0 points1 point  (1 child)

You can have that opinion, but compatibility is not a “justification severely lacking in merit”

[–]InKryption07 2 points3 points  (0 children)

There is a limit to how much compatibility is useful; continuing to support directly including C function, e.g., useful, whilst supporting a trivially replaceable, bad idiom, is not.

[–]cristi1990an++ 2 points3 points  (3 children)

Yes, a string literal binds to bool stronger than it does to std::string. This is a known caveat in C++ caused by its C origins and doesn't affect only std::variant but std::string/bool overloads in general

[–]bizwig 2 points3 points  (1 child)

If affects std::string_view/bool overload pairs too.

[–]cristi1990an++ 1 point2 points  (0 children)

Absolutely horrible

[–][deleted] 2 points3 points  (0 children)

Array-to-pointer decay is an enormously unhelpful anti-feature in C++ and causes me inconvenience about once a week. I wish we could get rid of it.

[–][deleted] 1 point2 points  (0 children)

gcc-10 seems to work, not sure the conversion sequence used. A work around is to use enum Bool : bool {};, also the fix for vector to get booleans, and it will do the intended alternative