Функция, которая возвращает неизвестный тип - PullRequest
8 голосов
/ 31 августа 2009
class Test
{
public:

 SOMETHING DoIt(int a)
 {
  float FLOAT = 1.2;
  int INT = 2;
  char CHAR = 'a';

  switch(a)
  {
  case 1: return INT;
  case 2: return FLOAT;
  case 3: return CHAR;
  }
 }
};


int main(int argc, char* argv[])
{  
 Test obj;
 cout<<obj.DoIt(1);    
    return 0;
}

Теперь, используя знание, что a = 1 подразумевает, что мне нужно вернуть целое число и т. Д., Есть ли в любом случае Doit () может вернуть переменную переменной типа данных?

По сути, чем я могу заменить ЧТО-ТО ?

PS: Я пытаюсь найти альтернативу возвращению структуры / объединения, содержащего эти типы данных.

Ответы [ 13 ]

25 голосов
/ 31 августа 2009

Вы можете использовать boost::any или boost::variant, чтобы делать то, что вы хотите. Я рекомендую boost::variant, потому что вы знаете коллекцию типов, которые вы хотите вернуть.


Это очень простой пример, хотя вы можете сделать гораздо больше с variant. Проверьте ссылку на дополнительные примеры:)

#include "boost/variant.hpp"
#include <iostream>

typedef boost::variant<char, int, double> myvariant;

myvariant fun(int value)
{
 if(value == 0)
 {
  return 1001;
 }
 else if(value  == 1)
 {
  return 3.2;
 }
  return 'V';
}

int main()
{
 myvariant v = fun(0);
 std::cout << v << std::endl;

 v = fun(1);
 std::cout << v << std::endl;

 v = fun(54151);
 std::cout << v << std::endl;
}

Выход:

1001
3.2
V

Я бы использовал boost::variant вместо union, потому что вы не можете использовать не-POD типы внутри union. Кроме того, boost::any замечательно, если вы не знаете тип, с которым имеете дело. В противном случае я бы использовал boost::variant, потому что он намного эффективнее и безопаснее.


Ответ на отредактированный вопрос: Если вы не хотите отправлять Boost с вашим кодом, взгляните на bcp. Описание bcp по той же ссылке:

Утилита bcp - это инструмент для извлекая подмножества Boost, это полезно для авторов Boost, которые хотят раздавать свою библиотеку отдельно от Boost, и для пользователей Boost, которые хочу распространять подмножество Boost с их применением.

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

8 голосов
/ 31 августа 2009

C ++ является языком со строгой типизацией и не имеет понятия неизвестного типа. Вы можете попробовать использовать boost :: any, который может (вроде) указывать любой тип. Однако я бы усомнился в дизайне вашей функции.

5 голосов
/ 31 августа 2009

Если вы знаете тип во время компиляции, вы можете использовать шаблоны. Если тип зависит от времени выполнения, то использование шаблонов не вариант.

class Test
{
  template<int> struct Int2Type {};
  template<>    struct Int2Type<1> { typedef int value_type; };
  template<>    struct Int2Type<2> { typedef float value_type; };
  template<>    struct Int2Type<3> { typedef char value_type; };

public:
  template<int x> typename Int2Type<x>::value_type DoIt() {}; // error if unknown type used
  template<> typename Int2Type<1>::value_type DoIt<1>() { return 2; };
  template<> typename Int2Type<2>::value_type DoIt<2>() { return 1.2f; };
  template<> typename Int2Type<3>::value_type DoIt<3>() { return 'a'; };
};

int main()
{
  Test obj;
  cout << obj.DoIt<2>(); 
  return 0;
}
3 голосов
/ 31 августа 2009

РЕДАКТИРОВАТЬ: boost :: any, использующий bcp (спасибо AraK), кажется лучшим решением на сегодняшний день, но возможно ли доказать (до некоторой степени), что не существует ANSI C ++ решения этой проблемы?

Вы, кажется, немного смущены терминологией здесь.

Во-первых, давайте назовем это ISO C ++, не так ли? Он был стандартизирован ISO в 1998 году, и с тех пор именно на это ссылались люди, говоря о «стандарте C ++». Теперь, что вы подразумеваете под «решением ANSI C ++»?

  • Решение, которое аккуратно компилируется с использованием только ANSI (или ISO) C ++? Если это так, Boost является решением ANSI C ++
  • Решение уже реализовано в стандартной библиотеке ANSI C ++? Если это так, то нет, такого решения не существует (и не существует «доказательства», кроме «прочитайте языковой стандарт и посмотрите, сможете ли вы найти такой класс. Если вы не можете, его там нет»).
  • Решение, которое вы могли бы реализовать самостоятельно , используя только ANSI C ++. Тогда ответ «да, вы можете скопировать исходный код из Boost».

Я не могу представить, какое "доказательство" вы бы искали. C ++ - это документ в прозе. Это не математическое уравнение. Это не может быть «доказано», кроме как сказать «иди читать стандарт». Доказать, что что-то определено на языке или в стандартной библиотеке, легко - просто укажите, где в стандарте это описано. Но доказать, что что-то не является , в принципе невозможно - за исключением перечисления каждого отдельного предложения стандарта и документа, который ни один из них не описывает то, что вы ищете. И я сомневаюсь, что вы найдете кого-нибудь, кто готов сделать , что для вас.

В любом случае, правильное стандартное решение C ++ - для использования Boost. Это не тяжелое решение. Boost довольно легок в том, что вы можете включить ровно необходимых вам бит без каких-либо зависимостей от остальной части коллекции библиотеки.

Из того, что вы описали (легкое приложение для широкой пользовательской базы), нет никаких причин не использовать Boost. Это может упростить ваш код и уменьшить количество ошибок, вызванных попытками изобретать велосипед. При распространении скомпилированного исполняемого файла он имеет нулевую стоимость. Библиотека Boost.Any, как и большая часть Boost, предназначена только для заголовков и просто компилируется в ваш исполняемый файл. Отдельные библиотеки не должны распространяться.

Ничего нельзя получить, пытаясь заново изобрести колесо. Ваш исполняемый файл не будет меньше или эффективнее, но будет более ошибочным.

И я готов поспорить, что ваше решение домашнего приготовления не будет ANSI C ++. Он будет опираться на некоторую форму неопределенного поведения. Если вам нужно решение ANSI-C ++, лучше всего поднять Boost.

3 голосов
/ 31 августа 2009

Обычный способ добиться чего-то подобного - это C, который не всегда работает в C ++, с помощью объединения и поля типа:

enum SomeType { INT, FLOAT, CHAR };
struct Something
{
    SomeType type;
    union
    {
        int i;
        float f;
        char c;
    };
};

Something DoIt(int a)
{
    Something s;
    switch (a)
    {
      case 1:
        s.type = INT;
        s.i = 2;
        break;
      case 2:
        s.type = FLOAT;
        s.f = 1.2;
        break;
      case 3:
        s.type = CHAR;
        s.c = 'a';
        break;
      default:
        // ???
    }
    return s;
}

Это не работает в C ++, когда одним из возможных типов значений является класс с нетривиальным конструктором, потому что не всегда понятно, какой конструктор следует вызывать. Boost.Variant использует более сложную версию этого подхода для предоставления такого рода конструкции для любых типов значений в C ++.

3 голосов
/ 31 августа 2009

Вы можете использовать структуру, содержащую void*, указывающую на значение, которое вы хотите вернуть, вместе с size_t, который указывает размер возвращаемого объекта. Примерно так:

struct Something {
    void *value;
    size_t size;
};

Помните, что void* должен указывать на значение, находящееся в куче (то есть динамически распределяется с использованием new или malloc), и вызывающая сторона должна позаботиться об освобождении выделенного объекта.

Сказав это, я думаю, что это в целом плохая идея.

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

3 голосов
/ 31 августа 2009

Использование boost :: any :

boost::any DoIt(int a)
{
    float FLOAT = 1.2;
    int INT = 2;
    char CHAR = 'a';

    switch(a)
    {
    case 1: return boost::any(INT);
    case 2: return boost::any( FLOAT);
    case 3: return boost::any( CHAR);
    }
}
1 голос
/ 31 августа 2009

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

typedef union {
  int i;
  float f;
  char c;
} retType;

retType DoIt(int a){
  retType ret;

  float FLOAT = 1.2;
  int INT = 2;
  char CHAR = 'a';

  switch(a)
  {
    case 1: ret.i = INT; break;
    case 2: ret.f = FLOAT; break;
    case 3: ret.c = CHAR; break;
  }
  return ret;
}
0 голосов
/ 31 августа 2009

Я думаю, что проблема в дизайне этой функции. Вы пробовали перегрузку?

class Test
{

public:

int DoIt(int a) {

  int INT = 2;
   return INT;

} 

float DoIt(float a) {

float FLOAT = 1.2; 
return FLOAT;

} 

char DoIt(char a) {

char CHAR = 'a'; 
return CHAR;

} 

};


int main(int argc, char* argv[])
{       
    Test obj;

//....

switch(a)
case 1: 
    cout<< obj.DoIt(1);    
break;

case 2:
cout<< obj.DoIt(1.01);   
break;

case 3:
cout<< obj.DoIt("1");   
break;

    return 0;
}

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

0 голосов
/ 31 августа 2009

Вместо этого вы можете перейти по ссылке, а также сохранить тип и проверить, работает ли он одновременно, и не потребует дополнительной библиотеки (ваш тип решения ANSI C ++):

bool DoIt (int i, int & r1)
{
  if (i==1) {r1 = 5; return true}
  return false;
}

bool DoIt (int i, double & r2)
{
  if (i==2) {r2 = 1.2; return true}
  return false;
}

...

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

...