«Материализация» объекта известного типа для вывода типа C ++ - PullRequest
2 голосов
/ 25 октября 2019

Следующий код делает все, что мне нужно, в каждом C ++ 11 и более поздних компиляторах, которые я пробовал. Таким образом, на практике для моих целей это работает (и, как ожидается, будет работать, по крайней мере, в Linux, в обозримом будущем по историческим причинам). Однако с точки зрения юриста языка этот код недопустим, поскольку он содержит конструкцию (разыменование указателя на несуществующий объект), которая формально является UD, даже если эта разыменование фактически никогда не выполняется.

#include <type_traits>
#include <iostream>

namespace n1 {

struct int_based { int base = 0; };
inline int get_base(int_based ib) { return ib.base; }

}

namespace n2 {

struct double_based;
double get_base(const double_based&);

}

template <typename T>
using base_type = decltype((get_base(*(T*)nullptr)));

int main() {
    auto isInt = std::is_same<base_type<n1::int_based>, int>::value;
    auto isDouble = std::is_same<base_type<n2::double_based>, double>::value;
    auto unlike = std::is_same<base_type<n1::int_based>, double>::value;
    std::cout << isInt << isDouble << unlike << std::endl;
    return 0;
}

Код выполняет поиск Кенига для сопоставления типов и выводит сопоставленный тип, используя сигнатуры функций, которые я не хотел бы изменять. В этом примере тип double_based является неполным;в моих реальных случаях использования типы должны быть завершены, но не гарантировано, что они будут DefaultConstructible. Фактический код является частью логики безопасной сериализации.

Вопрос заключается в следующем: существует ли стандартизированный способ "материализации" объекта типа параметра шаблона T для использования в * 1009? * в этом коде, или невозможно иметь такое соответствие стандарту соответствия типов без предварительно созданного объекта типа источника?

Замена параметров функции указателями на объекты является уродливой и не решаетпроблема, так как неясно, что эти функции должны делать с аргументом nullptr в общем случае, не вводя еще один UB.

Ответы [ 3 ]

3 голосов
/ 25 октября 2019

То, что вы ищете, это std::declval. Эта функция возвращает заданный вами тип, чтобы вы могли работать с объектом этого типа в неоцененном контексте. Это превращает

template <typename T>
using base_type = decltype((get_base(*(T*)nullptr)));

в

template <typename T>
using base_type = decltype((get_base(std::declval<T>())));

Обратите внимание, что не существует определения для std::declval. Если вы попытаетесь использовать

T foo = std::declval<T>();

, значит, ваша программа некорректна.

3 голосов
/ 25 октября 2019

На самом деле, этот код в порядке. (Это правда, что UB разыменовывает нулевой указатель и связывает результат со ссылкой, или разыменовывает нулевой указатель и обращается к полученному lvalue. Однако, когда выполнение программы фактически не оценивает эти конструкции, возникаетнет UB.)

Но это правда, что std::declval<T>() является предпочтительной идиомой. В отличие от трюка с нулевым указателем, std::declval<T>() является «безопасным»: если вы случайно используете его в потенциально оцененном контексте, произойдет ошибка времени компиляции. Это также намного менее безобразно.

2 голосов
/ 25 октября 2019

Вы можете использовать std::declval:

template <typename T>
using base_type = decltype((get_base(std::declval<T>())));
...