Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't the compiler generate compile errors if an incorrect argument type is passed to a struct initialiser list?

Tags:

c++

c++11

struct

I have defined a struct, which has a constructor:

struct MyStruct
{
    MyStruct(const int value)
        : value(value)
    {
    }
    int value;
};

and the following objects:

int main()
{
    MyStruct a (true);
    MyStruct b {true};
}

But I haven't received any compile errors, either with MVS2015 or Xcode 7.3.1.

  1. Why am I not getting any compile errors?
  2. How do I make the compiler help me detect this? (Initially, the struct was written to have bool data, but after some time, code changed and bool became int and several bugs were introduced.)
like image 723
T M Avatar asked May 12 '16 15:05

T M


3 Answers

A bool can be implicitly converted to an int in a way that's value preserving. The only disallowed conversions with brace initialization are narrowing conversions (e.g. the reverse bool{42}).

If you want to ensure that your class is constructible only with int, then the direct way is simply to delete all the other constructors:

struct MyStruct
{
    explicit MyStruct(int i) : value(i) { }

    template <typename T>
    MyStruct(T t) = delete;

    int value;
};

Here, MyStruct{true} and MyStruct(false) will yield calls to MyStruct::MyStruct<bool>, which is defined as deleted and hence is ill-formed.

The advantage of this over static_assert is that all the type traits will actually yield the correct values. For instance, std::is_constructible<MyStruct, bool> is std::false_type.

like image 164
Barry Avatar answered Nov 20 '22 13:11

Barry


Here's a construction that allows you to only initialize your class from an int value:

#include <type_traits>

struct MyStruct
{
    template <typename T>
    MyStruct(T t) : value(t)
    {
        static_assert(std::is_same<T, int>::value, "Bad!");
    }

    int value;
};

That's because the template argument deduction required by this constructor template will produce the exact type of the argument and not perform conversions, so you can perform tests on that type.

You should perhaps also or instead use SFINAE to constrain the constructor, so that MyStruct doesn't present itself as constructible from anything.

Furthermore, you should probably also make the constructor template explicit so that random integers don't become MyStruct instances.

In other words, I'd write it like so:

struct MyStruct
{
    template <typename T,
              typename = std::enable_if_t<std::is_same<T, int>::value>>
    MyStruct(T t) : value(t) {}

    // ...
like image 34
Kerrek SB Avatar answered Nov 20 '22 15:11

Kerrek SB


The simplest solution is to declare a bool constructor as deleted isn't it?

struct MyStruct
{
    MyStruct(bool) = delete;

    MyStruct(const int value)
    : value(value)
    {
    }
    int value;
};

example error output:

...
/Users/xxxxxxx/play/fast_return/skeleton/main.cpp:68:14: error: call to deleted constructor of 'MyStruct'
    MyStruct b {true};
             ^ ~~~~~~
/Users/xxxxxxx/play/fast_return/skeleton/main.cpp:57:9: note: 'MyStruct' has been explicitly marked deleted here
        MyStruct(bool) = delete;
        ^
2 errors generated.
like image 8
Richard Hodges Avatar answered Nov 20 '22 14:11

Richard Hodges