Какие отношения типов использовать в SFINAE для конструктора с perfect forwarding?

191
28 февраля 2018, 09:32

Несколько запутался с проверкой типов в SFINAE. Допустим, есть:

class Foo
{
public:
    std::string member;
    std::string member2;
    template <
        typename T, // Parameter 1.
        typename U, // Parameter 2.
        typename Enable = typename std::enable_if <
        std::is_constructible<std::string, T>::value &&
        std::is_constructible<std::string, U>::value>::type>
    Foo(T&& member, U&& member2) :
        member{std::forward<T>(member)},
        member2{std::forward<U>(member2)}
    {}
};

Не пойму, что нужно использовать, std::is_constructible или же std::is_convertible или std::is_same? Про последний, так понимаю жесткое совпадение нам обеспечено, можно еще std::decay только добавить. Однако хотелось бы строку инициализировать С-массивом или, например, вектор через std::initializer_list. Про std::is_convertible знаю, что пропустит, например, преобразование float в int, однако в таком коде:

template<class D>
    impl_ptr(pointer p, D&& d,
             typename std::enable_if<
                std::is_convertible<D, deleter_type>::value,
                dummy_t_
             >::type = dummy_t_()) noexcept
    : ptr_(std::move(p), std::forward<D>(d)) {}

используется (этот пример - конструктор smart-указателя для реализации pimpl, D и deleter_type - это удалители, ptr_ имеет тип std::unique_ptr).

Answer 1

std::is_convertible подразумевает именно и только неявные преобразования, со всеми сопутствующими ограничениями, налагаемыми на неявные преобразования. Он не будет принимать во внимание конвертирующие конструкторы и операторы приведения типа, помеченные как explicit. Он не будет выполнять более одного пользовательского преобразования в последовательности преобразований.

std::is_constructible напрямую рассматривает именно конструкторы, причем независимо то того, помечены ли они как explicit.

То есть разделение тут совершено четкое и [примерно] соответствует разнице между прямой инициализацией T a(b) и инициализацией копированием T a = b. Даже в С++17 различия между этими формами инициализации не сводятся только к explicit.

Выбирайте, что именно вам нужно в вашем случае.

У вас сейчас is_constructible, что фактически вытаскивает наружу все конструкторы std::string, включая конструкцию из аллокатора. Вам нужна такая возможность? Подозреваю, что исходный замысел был в том, чтобы разрешить только конструкцию из разнообразных видов строк. А тогда уместнее именно is_convertible.

Answer 2

В подобных случаях лучше использовать std::is_constructible, потому что вы передаёте аргумент в конструктор, а значит и проверять вам нужно существование конструктора для данных аргументов.

Но для конкретно приведённого примера лучше использовать string_view (если доступен), или принимать стоки по значению и передавать через std::move. Вариант с шаблонной реализацией создаёт больше проблем, чем пользы.

А std::is_convertible для удалителей в умных указателях используется из-за того, что удалителем может быть лямбда, которая неявно преобразуется в указатель на функцию, но указатель на функцию не конструируется напрямую из лямбды.

READ ALSO
LLVM. AddressSanitizer.Is a value operand of store Instruction function parameter

LLVM. AddressSanitizer.Is a value operand of store Instruction function parameter

В файле AddressSanitizercpp есть функция AddressSanitizer::instrumentStoreInstruction(

220
Пауза консоли в C++

Пауза консоли в C++

Какие функции в Visual Studio (помимо system("pause");) останавливают закрытие консоли

245
Собрать и объяснить код (пример с сайта)

Собрать и объяснить код (пример с сайта)

Имееться код hash-алгоритмаТак как его надо исследовать, необходимо его запустить (для начала)

257
Парсинг XSD С++

Парсинг XSD С++

Привет всемВозникла задача, на основе XML и XSD динамически подгружать интерфейс

199