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...
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));
}