Изменение типа возвращаемого значения функции без специализации шаблонов. С++

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

Я хочу создать функцию, которая анализирует переменную int, bool или float из строки. Например...

Int value = parse("37");
Float value = parse("3.14");
Bool value = parse("true");

Я понимаю, что я делаю эту функцию шаблоном, чтобы тип переменной определялся из списка аргументов, который всегда будет строкой. Есть ли другой способ сделать это с помощью С++?

Ответ 1

Это можно сделать с помощью функции преобразования

struct proxy {
    string str;
    proxy(string const &str):str(str) { }
    template<typename T> operator T() { 
        return boost::lexical_cast<T>(str); 
    }
};

proxy parse(string const &str) { return proxy(str); }

Теперь вам просто нужно сделать

float a = parse("3.1");

И он должен хорошо работать. Кстати, вы можете просто использовать класс напрямую. Я рекомендую переименовать его в conversion_proxy, чтобы указать на то, что он просто прокси-сервер для происходящего преобразования, но сам он не выполняет преобразование

struct conversion_proxy {
    string str;
    conversion_proxy(string const &str):str(str) { }
    template<typename T> operator T() { 
        return boost::lexical_cast<T>(str); 
    }
};

float a = conversion_proxy("3.1"); 

Ответ 2

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

template<typename T> T parse(const string& str) { /* do stuff for other types */ }
template<> int parse<int>(const string& str) { /* do stuff for ints */ }
template<> double parse<double>(const string& str) { /* do stuff for doubles */ }
template<> bool parse<bool>(const string& str) { /* do stuff for bools */ }
// etc.

И затем вызывается как

int value = parse<int>("37");
double value = parse<double>("3.14");
bool value = parse<bool>("true");

Если вы уже знали, что это просто игнорирует этот ответ, но из вашего вопроса не ясно, что вы знаете, что это возможно.

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

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

#include <sstream>
template<typename T> T parse(const string& str) 
{
  T t;
  std::istringstream sstr(str);
  sstr >> t;
  return t;
}

Это будет работать для любого построчного по умолчанию типа, извлекаемого из потока, который включает все встроенные модули.

Ответ 3

Вы можете передать свой выходной аргумент как указатель или ссылку.

Вот так:

template<class T> void parse(const std::string &input, T& output);

Затем код выглядит следующим образом:

double d; parse(input, d);
int i; parse(input, i);

должен работать.

Однако ваш код выглядит идеально подходящим для std:: istringstream, который будет просто:

istringstream is(input);
input >> d;

Если у вас несколько сложное форматирование, трюк, с которым мне очень повезло, включает создание пользовательских объектов с помощью пользовательского оператора → , который извлекает данные.

Тогда это может быть как:

istringstring is(input);
input >> LineExtracter(x, y, d);

Ответ 4

Я согласен с тем, что был немного быстрее меня. Используйте операторы литья.

#include <iostream>
#include <string>
#include <sstream>

class Convertible
{
public:
    int m_Integer;
    bool m_Bool;
    double m_Double;

    Convertible() : m_Integer(0), m_Bool(false), m_Double(0.0) {};

    operator int() const
    {
        return m_Integer;
    }
    operator bool() const
    {
        return m_Bool;
    }
    operator double() const
    {
        return m_Double;
    }
};

Convertible parse(std::string data)
{
    Convertible l_result;

    std::istringstream converter(data);
    converter >> l_result.m_Integer;

    std::istringstream converter2(data);
    converter2 >> l_result.m_Bool;

    std::istringstream converter3(data);
    converter3 >> l_result.m_Double;

    return l_result;
}

void main()
{
    int l_convertedInt = parse("2");
    bool l_convertedBool = parse("true");
    double l_convertedDouble = parse("3.14");

    std::cout << "Converted '2' to " << l_convertedInt << std::endl;
    std::cout << "Converted 'true' to " << l_convertedBool << std::endl;
    std::cout << "Converted '3.14' to " << l_convertedDouble << std::endl;
}

Ответ 5

К сожалению, это невозможно. В С++ невозможно перегрузить функцию на основе возвращаемого значения. Вы либо должны иметь 3 функции, ParseInt, ParseFloat и ParseBool, либо использовать шаблон функции.

Ответ 6

Вы можете вернуть void *, а затем приложить результат по мере необходимости.

Я советую против этого. С++ - это строго типизированный язык. Преимущество этого заключается в том, что компилятор может ловить ошибки раньше, чем динамически типизированный язык.

Ответ 7

В С++ такого типа поведения невозможно. Чтобы быть допустимым, это потребовало бы возможности определять функции с тем же именем в той же области действия, которые отличались только типом возврата. Это не является законным в С++.

С++ может выполнять некоторую специализацию типа возвращаемого типа, например, ковариантные типы возвращаемых данных при переопределенных виртуальных функциях. Но он не поддерживает то, что вы ищете.

Ответ 8

Здесь моя адаптация @Tyler McHenry answer для моей ситуации, когда аргумент parse() является типом, отличным от строки.

Обратите внимание, что я обнаружил, что мне нужно было указать специализацию по шаблону, чтобы избежать предупреждения о преобразовании (float to int).

(См. также живая демонстрация.)

#include <iostream>

struct MyUnion
{
public:
  union {
    bool bool_value;
    int int_value;
    float float_value;
  };
};

template<typename T> T parse(const MyUnion& h)
{
  T t;

  if (typeid(T) == typeid(bool)) {
    t = h.bool_value;
  } else if (typeid(T) == typeid(int)) {
    t = h.int_value;
  } else if (typeid(T) == typeid(float)) {
    // t = h.float_value; // see **Warning** below; use float specialization instead
  }

  return t;
}

// 'float' template specialization to avoid conversion warning.
template<> float parse(const MyUnion& h)
{
  return h.float_value;
}

int main()
{
  MyUnion mu1; mu1.bool_value = true;
  MyUnion mu2; mu2.int_value = 42;
  MyUnion mu3; mu3.float_value = 3.14159;

  std::cout << "As bool: "  << parse<bool>(mu1)  << std::endl;
  std::cout << "As int: "   << parse<int>(mu2)   << std::endl;
  std::cout << "As float: " << parse<float>(mu3) << std::endl;
}

// **Warning**
// In function 'T parse(const Heterogeneous&) [with T = int]':
// Line 22: warning: converting to 'int' from 'const float'