Я хотел бы обновить функции преобразования, которые имеют следующие сигнатуры:
template<typename In, typename Out>
bool Convert(const In& p_in, Out& p_out);
// Returns `true` if the conversion succeeded, `false` otherwise.
чтобы вместо этого я мог использовать std::optional. Что-то вроде (параметры шаблона уведомления были переключены):
template<typename Out, typename In>
std::optional<Out> Convert(const In& p_in);
Что касается шаблонов, произошли большие изменения: один из типов шаблонов теперь используется в возвращаемом типе Convert. Из-за этого компиляция завершается неудачно, если я не укажу параметры функции. Пример:
#include <optional>
class A
{
public:
A() = default;
A(int p_value)
: m_value(p_value)
{}
private:
int m_value = 0;
};
template<typename In, typename Out>
bool Convert(const In& p_in, Out& p_out)
{
return false;
}
template<>
bool Convert(const int& p_in, A& p_out)
{
p_out = A(p_in);
return true;
}
template<typename Out, typename In>
std::optional<Out> Convert2(const In& p_in)
{
return std::nullopt;
}
template<>
std::optional<A> Convert2(const int& p_in)
{
return A(p_in);
}
int main()
{
const int in = 2;
// Fine, but not ideal since we have `std::optional` available:
A out;
const bool result1 = Convert(in, out);
// Failed:
//const std::optional<A> result2 = Convert2(in);
// Ok, but sadly A is repeated at called site:
const std::optional<A> result3 = Convert2<A>(in);
return 0;
}
Вот сообщение об ошибке (gcc), если я раскомментирую второй вызов:
<source>: In function 'int main()':
<source>:62:46: error: no matching function for call to 'Convert2(const int&)'
62 | const std::optional<A> result2 = Convert2(in);
| ~~~~~~~~^~~~
<source>:30:20: note: candidate: 'template<class Out, class In> std::optional<_Tp> Convert2(const In&)'
30 | std::optional<Out> Convert2(const In& p_in)
| ^~~~~~~~
<source>:30:20: note: template argument deduction/substitution failed:
<source>:62:46: note: couldn't deduce template parameter 'Out'
62 | const std::optional<A> result2 = Convert2(in);
| ~~~~~~~~^~~~
Используя auto, мне удалось заставить это работать:
template<typename In>
auto Convert(const In& p_in)
{
return std::nullopt;
}
template<>
auto Convert(const int& p_in)
{
return std::make_optional<A>(p_in);
}
Преимущество этого заключается в том, что его можно использовать по своему усмотрению на месте вызова:
const std::optional<A> result4 = Convert3(in)
Но я чувствую, что сигнатура функции теперь гораздо менее информативна о том, что делает функция. Чтобы узнать, возвращается ли std::optional<A>, нужно прочитать реализацию.
Есть ли способ заставить это работать без необходимости указывать шаблонные типы на месте вызова?
template<typename Out, typename In>
std::optional<Out> Convert(const In& p_in)
{
// ?
}
template<>
std::optional<A> Convert(const int& p_in)
{
// ?
}
int main()
{
const std::optional<A> result = Convert(in)
return 0;
}
Источники о Godbolt: https://godbolt.org/z/bYjrcPsPx.
Это работает, однако вы теряете информацию std::optional на месте вызова. Если возможно, я бы хотел увидеть это, не вдаваясь в определения.
const std::optional result = Convert<A>(in); также должно работать благодаря CTAD, если все функции Convert возвращают некоторое количество std::optional (std::nullopt не std::optional).
В общем, я предпочитаю перегрузку функций для функций преобразования, потому что обычно мне нужно очень точно указывать тип ввода и желаемый тип вывода. Итак, если вы спросите меня, я думаю, что шаблон функции, возможно, не самый полезный подход (это я предполагаю, что вы хотите конвертировать типы, а не значения).
Если вы можете использовать auto, это означает, что вы знаете правильный Out для каждого типа ввода (например, A для int). Это правда?





Две вещи:
Во-первых, вы не хотите менять порядок параметров шаблона. Должен быть:
template <typename In, typename Out>
std::optional<Out> convert( In input );
Это позволит, по крайней мере, сделать вывод о типе шаблона по типу ввода.
Во-вторых, при вызове функции вам придется явно указать тип Out, поскольку нет ничего, что позволило бы компилятору его вывести. Так:
X out = convert<X>( in );
Компилятор (обычно) не использует возвращаемое значение при выводе параметров шаблона.
На самом деле возможно заставить компилятор определить тип Out, но это редко стоит дополнительных сложностей. Компилятор будет учитывать целевые преобразования, поэтому фактическая функция преобразования будет выглядеть примерно так:
template <typename In>
Helper<In> convert( In value ) { return Helper<In>( value ); }
Тогда класс Helper будет содержать несколько операторов преобразования:
template <typename In>
class Helper
{
X m_value;
public:
Helper( X value ) : m_value( value ) {}
template <typename Out>
operator std::optional<Out>()
{
// actual conversion of m_value here.
}
};
Однако в целом реализация этого для нескольких типов была бы чрезвычайно болезненной, и в целом я не думаю, что это сильно облегчило бы жизнь пользователю: вы не могли бы писать такие вещи, как:
auto tmp = convert( anX );
if ( tmp.has_value() ) {
Y value = tmp.value();
// ....
}
(Вероятно, это случай злоупотребления auto, но некоторые люди, похоже, предпочитают его. Здесь не нужно писать std::optional.)
С template <typename In, typename Out> сделать X out = convert<X>( in ); не получится и пусть In выводится. Хотя наоборот сработало бы.
Ваш первый пункт неверен. Out должен быть первым: вход еще можно вывести.
Естественный способ обновить код для использования std::optional:
template <typename In, typename Out>
bool OldConvert(const In& p_in, Out& p_out);
template <typename Out, typename In>
std::optional<Out> Convert(const In& p_in)
{
Out res;
if (OldConvert(p_in, res)) { // Possibly put code directly here
return std::make_optional(res);
} else {
return std::nullopt;
}
}
и использование меняется с
A out;
if (Convert(in, out)) {
use(out);
}
к
if (const auto out = Convert<A>(in)) {
use(*out);
}
тогда как auto может быть текстуально заменено на std::optional<A> (если вы хотите быть явным) или std::optional (чтобы избежать повторения A)
Мне это кажется идиоматическим.
У вас есть другая альтернатива с использованием (ab) оператора преобразования для получения синтаксиса:
if (const std::optional<A> out = Converter(in)) {
use(*out);
}
с
template <typename In>
struct ConverterHelper
{
template <typename Out>
operator std::optional<Out>() && {
return Convert<Out>(arg);
}
const In& arg;
};
template <typename In>
auto Converter(const In& p_in)
{
return ConverterHelper{p_in};
}
template<class T>struct tag_t{};
template<class T>constexpr tag_t<T> tag = {};
template<class F>
struct deduce_out {
F f;
template<class Out>
operator Out()&&{
return f(tag<Out>);
}
};
Использовать:
template<class In>
auto convert(In&& in){
return deduce_out{
[&]<class Out>
(tag_t<std::optional<Out>>)
->std::optional<Out>
{
// code
}
}
std::optional<double> d = convert(7);
const auto result = Convert<A>(in);? Попытка избежатьAсправа выглядит очень странно.