Проблема в SFINAE с `is_constructible` в конструкторе

У меня проблема с пониманием, почему следующий код не компилируется:

#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;
}

и выдается ошибка:

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

Вот код: https://onecompiler.com/cpp/42n6d9sj8

Пока просто меняю это:

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

к этому

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

исправляет это.

Мое понимание: Я возвращаюсь data.at(0), что будет int32_t так U = int32_t. В std::is_constructible<U> будет проверяться, является ли int32_t конструктивным (истина). Поэтому конструктор должен быть включен и должен быть создан экземпляр Foo...

Для второй версии

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

Я проверяю, может ли T быть построено из U, оба являются типами int32_t, поэтому я проверяю, может ли int32_t быть построено из int32_t, и это проходит, потому что это верно, поскольку U совпадает с T. Я не думаю, что это будет реально исправить, но у меня нет другого представления, как должен выглядеть правильный код в этом случае...

🤔 А знаете ли вы, что...
C++ используется для создания компиляторов, интерпретаторов и других инструментов разработки.


1
72
2

Ответы:

Проблема в том, что вы используете ссылку на пересылку, значение которой в параметре конструктора U выводится как std::int32_t&, а не std::int32_t.

Итак, std::is_constructible<U>::value терпит неудачу, потому что U есть std::int32_t&.

Вы можете решить/проверить это, удалив && из параметра функции.

template <typename T, typename E = Error>
class Foo
{
public:


template <typename U = T,
              std::enable_if_t<std::is_constructible<U>::value, bool> = true>   
//------v------------------------------------------------->removed && from here
    Foo(U other)
    {
        new (&value_) T(std::move<U>(other));
    }
//other code as before
};

Рабочая демо без пересылки ссылки .

Второй способ заставить эту работу работать — удалить ссылку из U перед передачей ее в качестве аргумента в std::constructible, как это сделано в ответе Теда.


Решено

Вы используете ссылку для пересылки, и std::is_constructible<int32_t&>::value оценивается как false. Вам необходимо std::remove_reference в конструкторе SFINAE'd:

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