Можно ли сохранить как переменную с пометкой constexpr, так и переменную длительности статического хранения через тип класса с руководством по дедукции?
Рассмотрим следующий фрагмент кода:
template <typename T>
struct wrap {
T thing;
constexpr wrap(T thing) : thing(thing) {}
};
template <typename T>
wrap(const T&) -> wrap<T>;
template <wrap V>
void fun();
struct X {
int a;
};
int main() {
constexpr auto a1 = &X::a;
static const auto a2 = &X::a;
fun<a1>();
fun<a2>(); // Doesn't compile
}
Теперь дело в том, что можно успешно пройти
wrap
без необходимости явно указывать параметр шаблона, но не имеющий длительности статического хранения .
Если я изменю приведенное выше руководство по выводам на это:
template <typename T>
wrap(const T&) -> wrap<const T&>; // <-- Note the 'const T&' here instead of plain 'T'
Если возможно, как изменить приведенный выше код, чтобы можно было передать оба
a1
а также
a2
соответственно без необходимости явно указывать такие типы, как
fun<wrap<decltype(a1)>{a1}>()
или
fun<wrap<const decltype(a2)&>{a2}>()
?
1 ответ
Конечно, это возможно. Однако, прежде чем я объясню решение, позвольте мне предположить, что вы излишне усложнили проблему, настаивая на конкретном интерфейсе, который (на мой взгляд) на самом деле не чище, чем альтернативы. По сути, вы просите либо взять по значению, либо по ссылке, в зависимости от того, какой из них на самом деле правильно сформирован. В случае его можно взять только по значению; его нельзя взять по ссылке, потому что он не имеет статической длительности хранения. В случае , он не может быть взят по значению, потому что он не был объявлен
constexpr
, но его можно взять по ссылке, поскольку он имеет статическую продолжительность хранения.
Код, использующий предложенную вами версию, трудно читать, потому что читатель, увидев
fun<arg>
, не сразу знает, берется ли по значению или по ссылке. Читатель должен сделать вывод о том, какой именно, основываясь на собственном знании читателем того, является ли
arg
является разрешенным параметром шаблона, не являющимся типом, по значению или по ссылке . Кроме того, некоторые аргументы могут квалифицироваться как оба, и в этом случае читатель также должен знать, какое значение по умолчанию выбрал разработчик для этого случая, чтобы знать, что происходит.
Опять же, это только мое мнение: было бы намного проще, если бы вы написали отдельные функции, возможно, вызывая их и , где
fun_val<a1>()
а также
fun_ref<a2>()
хорошо сформированы. Для этого мы должны определить два класса-оболочки, один из которых принимает аргумент по значению, а другой — по ссылке:
template <typename T>
struct wrap_value {
using value_type = T;
T thing;
constexpr wrap_value(T thing) : thing(thing) {}
};
template <typename T>
wrap_value(const T&) -> wrap_value<T>;
template <typename T>
struct wrap_reference {
using value_type = T;
const T& thing;
constexpr wrap_reference(const T& thing) : thing(thing) {}
};
template <typename T>
wrap_reference(const T&) -> wrap_reference<T>;
template <wrap_value V>
void fun_val() {
std::cout << "value\n";
}
template <wrap_reference V>
void fun_ref() {
std::cout << "reference\n";
}
struct X {
int a;
};
int main() {
constexpr auto a1 = &X::a;
static const auto a2 = &X::a;
static const int x = 42;
fun_val<a1>(); // OK
fun_ref<a1>(); // Error
fun_val<a2>(); // Error
fun_ref<a2>(); // OK
fun_val<x>(); // OK; uses value of x
fun_ref<x>(); // OK; uses address of x
}
Теперь, если вы настаиваете на наличии одного имени, то ключ в том, чтобы распознать это и иметь один и тот же тип, поэтому одно приложение CTAD никогда не сможет определить правильный тип оболочки, чтобы сделать вызов правильно сформированным. Вместо этого вы должны использовать SFINAE с двумя перегрузками: та, которая недействительна для данного аргумента шаблона (поскольку она принимает аргумент по значению (соответственно ссылке), который не может быть принят по значению (соответственно ссылке)) отбрасывается. По сути, переименуйте оба
fun_val
а также
fun_ref
в приведенном выше примере просто
fun
:
template <wrap_value V>
void fun() {
std::cout << "value\n";
}
template <wrap_reference V>
void fun() {
std::cout << "reference\n";
}
Это прекрасно работает в случае
a1
а также
a2
, для которого кандидатом является только одна из двух перегрузок. Но в случае
x
, это будет неоднозначно. Допустим, вы хотите принудительно выбрать перегрузку по значению в этом случае. Мы можем сделать это, вставив ограничение, которое делает перегрузку по ссылке не кандидатом:
template <wrap_reference V> requires(!requires { fun<wrap_value<typename decltype(V)::value_type>(V.thing)>(); })
void fun() {
std::cout << "reference\n";
}
Вы можете просмотреть полный рабочий пример здесь.