c++c++14sfinaetype-traits

Problem on SFINAE with `is_constructible` in constructor


I have a problem understanding why the following code fails to compile:

#include <iostream>
#include <vector>
#include <type_traits>

enum class Error
{
    OK = 0,
    NOK = 1
};

template <typename T, typename E = Error>
class Foo
{
public:
    template <typename U = T,
              std::enable_if_t<std::is_constructible<U>::value, bool> = true>
    Foo(U&& other)
    {
        new (&value_) T(std::forward<U>(other));
    }

private:
    union
    {
        T value_;
        E error_;
    };
};

// usage:
Foo<std::int32_t> Check(std::vector<std::int32_t>& data)
{
    // some code...
    return data.at(0); // here is the problem 
}

int main()
{
    std::vector<std::int32_t> data{1, 2};
    auto res = Check(data);
    return 0;
}

and error is produced:

could not convert ‘(& data)->std::vector::at(0)’ from ‘__gnu_cxx::__alloc_traits, int>::value_type’ {aka ‘int’} to ‘Foo’

Here is the code: https://onecompiler.com/cpp/42n6d9sj8

While just changing this:

std::enable_if_t<std::is_constructible<U>::value, bool> = true>

to this

std::enable_if_t<std::is_constructible<T, U>::value, bool> = true>

fixes it.

My understanding: I return data.at(0) which will be int32_t so U = int32_t. In std::is_constructible<U> there will be check if int32_t is constructible (true). So constructor should be enabled and an instance of Foo should be created...

For the second version

std::enable_if_t<std::is_constructible<T, U>::value, bool> = true>

I check if T can be constructed from U, both are int32_t types so I am checking if int32_t can be constructed from int32_t and it passes because it is true as U is the same as T. I do not think it will be a real fix here, but I do not have any other idea what the correct code should look like in this case...


Solution

  • You are using a forwarding reference and std::is_constructible<int32_t&>::value evaluates to false. You need to std::remove_reference in the SFINAE'd constructor:

    template <typename U = T,
              std::enable_if_t<
                  std::is_constructible<std::remove_reference_t<U>>::value,
    //                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^
                  bool> = true>
    Foo(U&& other) {
        new (&value_) T(std::forward<U>(other));
    }