Возврат необязательного параметра из шаблонной функции преобразования

Я хотел бы обновить функции преобразования, которые имеют следующие сигнатуры:

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.

const auto result = Convert<A>(in);? Попытка избежать A справа выглядит очень странно.
Evg 16.06.2024 16:40

Это работает, однако вы теряете информацию std::optional на месте вызова. Если возможно, я бы хотел увидеть это, не вдаваясь в определения.

BobMorane 16.06.2024 16:47
const std::optional result = Convert<A>(in); также должно работать благодаря CTAD, если все функции Convert возвращают некоторое количество std::optional (std::nullopt не std::optional).
Evg 16.06.2024 16:52

В общем, я предпочитаю перегрузку функций для функций преобразования, потому что обычно мне нужно очень точно указывать тип ввода и желаемый тип вывода. Итак, если вы спросите меня, я думаю, что шаблон функции, возможно, не самый полезный подход (это я предполагаю, что вы хотите конвертировать типы, а не значения).

Pepijn Kramer 16.06.2024 16:59

Если вы можете использовать auto, это означает, что вы знаете правильный Out для каждого типа ввода (например, A для int). Это правда?

Davis Herring 16.06.2024 23:54
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
98
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Две вещи:

Во-первых, вы не хотите менять порядок параметров шаблона. Должен быть:

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 выводится. Хотя наоборот сработало бы.

Ted Lyngmo 16.06.2024 17:49

Ваш первый пункт неверен. Out должен быть первым: вход еще можно вывести.

Yakk - Adam Nevraumont 16.06.2024 19:14
Ответ принят как подходящий

Естественный способ обновить код для использования 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);

Другие вопросы по теме

Достаточно ли предварительного объявления std::list, чтобы проверить, является ли тип T std::list?
Рекурсивный шаблон C++ с переменным числом вариантов
Ошибка компиляции при использовании функций шаблона C++, которые принимают в качестве аргументов другие функции, которые принимают ссылки на указатели
Специализация шаблона вне встроенного пространства имен функции, определенной внутри встроенного пространства имен
Как вывести в шаблон имя текущего тега? (Тот, в котором мы находимся)
Может ли быть нарушено ODR, если определение шаблона создается только с разными параметрами?
Вывести параметры шаблона для параметра шаблона std::variant
Функция constexpr, которая продолжает умножать число до тех пор, пока оно не станет «достаточно большим»
Требуется ли для создания экземпляров шаблонов классов использовать только указатель или ссылку на них?
Как объявить конструктор с аргументами ровно nRow*nCol типа T?