Функція перевантаження за типом повернення?


252

Чому більш основні статично набрані мови не підтримують функцію / метод перевантаження за типом повернення? Я не можу думати ні про що. Це здається не менш корисним або розумним, ніж підтримка перевантаження за типом параметра. Чому це так менш популярно?


2

Відповіді:


523

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

int func();
string func();
int main() { func(); }

ви не можете сказати, що func()викликається. Це можна вирішити кількома способами:

  1. Мати передбачуваний метод визначення того, яка функція викликається в такій ситуації.
  2. Щоразу, коли виникає така ситуація, це помилка часу компіляції. Однак є синтаксис, який дозволяє програмісту роз'єднуватись, наприклад int main() { (string)func(); }.
  3. Не мають побічних ефектів. Якщо у вас немає побічних ефектів і ви ніколи не використовуєте повернене значення функції, компілятор може уникнути будь-якого виклику функції в першу чергу.

Дві мови, якими я регулярно ( ab ) користуюсь перевантаженням за типом повернення: Perl та Haskell . Дозвольте описати, що вони роблять.

У Perl існує принципова відмінність між скалярним та списком контексту (та іншими, але ми будемо робити вигляд, що їх два). Кожна вбудована функція в Perl може робити різні речі залежно від контексту, в якому вона викликана. Наприклад, joinоператор змушує перераховувати контекст (на предмет, що з'єднується), а scalarоператор примушує скалярний контекст, тому порівняйте:

print join " ", localtime(); # printed "58 11 2 14 0 109 3 13 0" for me right now
print scalar localtime(); # printed "Wed Jan 14 02:12:44 2009" for me right now.

Кожен оператор в Perl робить щось у скалярному контексті і щось у контексті списку, і вони можуть бути різними, як показано на ілюстрації. (Це не тільки для випадкових операторів, таких як localtime. Якщо ви використовуєте масив @aу контексті списку, він повертає масив, а в скалярному контексті - повертає кількість елементів. Так, наприклад, print @aвиводиться з елементів, а print 0+@aдрукується розмір. ) Крім того, кожен оператор може форсувати контекст, наприклад, додавання +примушує скалярний контекст. Кожен запис у man perlfuncдокументах це. Наприклад, ось частина запису для glob EXPR:

У контексті списку повертає (можливо, порожній) список розширень імен файлів на значення, EXPRтаке як стандартна оболонка Unix /bin/csh. У скалярному контексті глобус повторюється через такі розширення імен файлів, повертаючи undef, коли список вичерпано.

Тепер, яке співвідношення між списком та скалярним контекстом? Ну, man perlfuncкаже

Запам’ятайте таке важливе правило: Не існує правила, яке б пов'язувало поведінку виразу в контексті списку з його поведінкою у скалярному контексті чи навпаки. Це може зробити дві абсолютно різні речі. Кожен оператор і функція вирішує, яке саме значення було б найбільш доцільним повернути в скалярному контексті. Деякі оператори повертають довжину списку, який був би повернутий у контексті списку. Деякі оператори повертають перше значення у списку. Деякі оператори повертають останнє значення у списку. Деякі оператори повертають кількість успішних операцій. Взагалі вони роблять те, що ви хочете, якщо ви не хочете послідовності.

тому справа не в тому, щоб мати єдину функцію, і тоді ви виконаєте просте перетворення в кінці. Насправді я вибрав localtimeприклад саме з цієї причини.

Таку поведінку мають не лише вбудовані модулі. Будь-який користувач може визначити таку функцію за допомогою wantarray, що дозволяє розрізняти список, скалярний та недійсний контекст. Так, наприклад, ви можете вирішити нічого не робити, якщо вас викликають у недійсному контексті.

Тепер ви можете поскаржитися, що це неправда перевантаження за значенням повернення, оскільки у вас є лише одна функція, яка відповідає контексту, в який вона викликана, а потім діє на цю інформацію. Однак це очевидно еквівалентно (і аналогічно тому, як Perl не дозволяє звичайно перевантажувати буквально буквально, але функція може просто вивчити свої аргументи). Більше того, це добре вирішує неоднозначну ситуацію, про яку було сказано на початку цієї відповіді. Perl не скаржиться, що не знає, до якого методу дзвонити; це просто називає. Все, що потрібно зробити, це зрозуміти, в якому контексті викликалася функція, яка завжди можлива:

sub func {
    if( not defined wantarray ) {
        print "void\n";
    } elsif( wantarray ) {
        print "list\n";
    } else {
        print "scalar\n";
    }
}

func(); # prints "void"
() = func(); # prints "list"
0+func(); # prints "scalar"

(Примітка. Іноді я можу сказати, що оператор Perl, коли я маю на увазі функцію. Це не важливо для цієї дискусії.)

Haskell застосовує інший підхід, а саме - не мати побічних ефектів. Він також має сильну систему типу, і тому ви можете писати код на зразок наступного:

main = do n <- readLn
          print (sqrt n) -- note that this is aligned below the n, if you care to run this

Цей код зчитує число з плаваючою комою зі стандартного вводу та друкує його квадратний корінь. Але що дивного в цьому? Ну, тип readLnє readLn :: Read a => IO a. Це означає, що для будь-якого типу, який може бути Read(формально, кожен тип, який є екземпляром Readкласу типів), readLnможе його прочитати. Звідки Haskell знав, що я хочу прочитати число з плаваючою комою? Ну, тип sqrtє sqrt :: Floating a => a -> a, що по суті означає, що sqrtможна приймати лише цифри з плаваючою комою як вхідні дані, і тому Haskell зробив висновок про те, що я хотів.

Що станеться, коли Haskell не може зробити те, що я хочу? Ну, є кілька можливостей. Якщо я взагалі не використовую повернене значення, Haskell просто не буде викликати функцію в першу чергу. Однак, якщо я зробити використовувати значення, що повертається, то Haskell буде скаржитися , що він не може визначити тип:

main = do n <- readLn
          print n
-- this program results in a compile-time error "Unresolved top-level overloading"

Я можу вирішити двозначність, вказавши потрібний тип:

main = do n <- readLn
          print (n::Int)
-- this compiles (and does what I want)

У будь-якому випадку, що означає вся ця дискусія, це те, що перевантаження зворотним значенням можливе і робиться, що відповідає частині вашого запитання.

Інша частина вашого питання полягає в тому, чому більшість мов цього не роблять. Я дозволю іншим відповісти на це. Однак кілька коментарів: головна причина, ймовірно, полягає в тому, що можливості для плутанини тут справді більше, ніж при перевантаженні за типом аргументу. Ви також можете переглянути обґрунтування з окремих мов:

Ада : "Може здатися, що найпростіше правило розв'язання перевантаження - використовувати все - всю інформацію з якомога ширшого контексту - для вирішення перевантаженої посилання. Це правило може бути простим, але не корисним. Це вимагає від читача людини сканувати довільно великі фрагменти тексту та робити довільно складні умовиводи (наприклад, (g) вище). Ми вважаємо, що кращим правилом є те, що робить явним завданням, який повинен виконувати читач людини чи компілятор, і це робить це завдання якомога природніше для людського читача ».

C ++ (підрозділ 7.4.1 розділу "Мова програмування на C ++" Bjarne Stroustrup): "Типи повернення не враховуються в роздільній здатності перевантаження. Причиною є збереження роздільної здатності для окремого оператора або функції виклику функції незалежно від контексту".

float sqrt(float);
double sqrt(double);

void f(double da, float fla)
{
    float fl = sqrt(da);     // call sqrt(double)
    double d = sqrt(da); // call sqrt(double)
    fl = sqrt(fla);            // call sqrt(float)
    d = sqrt(fla);             // call sqrt(float)
}

Якщо врахувати тип повернення, більше не можна було б дивитись на виклик sqrt()ізоляції та визначати, яка функція викликалася. "(Зауважте, для порівняння, що в Haskell немає неявних перетворень.)

Java ( специфікація мови Java 9.4.1 ): "Один із успадкованих методів повинен бути замінним типом повернення для всіх інших успадкованих методів, інакше виникає помилка часу компіляції." (Так, я знаю, що це не дає обгрунтування. Я впевнений, що обгрунтування дано Гослінгом у "Мові програмування Java". Можливо, у когось є копія? Я думаю, що це "принцип найменшого здивування" по суті. ) Однак, цікавий факт про Java: JVM дозволяє перевантажувати повернене значення! Це використовується, наприклад, у Scala , і до нього можна дістатись безпосередньо через Java, також граючи з внутрішніми.

PS. Як остаточне зауваження, насправді можна перевантажувати повернене значення в C ++ хитрістю. Свідок:

struct func {
    operator string() { return "1";}
    operator int() { return 2; }
};

int main( ) {
    int x    = func(); // calls int version
    string y = func(); // calls string version
    double d = func(); // calls int version
    cout << func() << endl; // calls int version
    func(); // calls neither
}

Чудовий пост, але ви, можливо, захочете уточнити, що таке читання (Рядок -> щось).
Томас Едінг

C ++ також дозволить вам перевантажувати повернене значення const / not const. stackoverflow.com/questions/251159 / ...
Geon

3
Для вашого останнього трюку з перевантаженням операторів примусу рядок "cout" працює іноді, але майже будь-яка зміна, внесена до коду, дає "неоднозначне перевантаження" оператору << ".
Стів

1
Я б наголосив на тому, щоб вимагати, щоб одна перевантаження була позначена як "бажана"; компілятор розпочнеться з прив'язки, використовуючи лише бажані перевантаження, а потім визначить, чи не будуть кращими будь-які небажані перевантаження. Крім іншого, припустимо, типи Fooта Barпідтримують двонаправлене перетворення, а метод використовує тип Fooвнутрішньо, але повертає тип Bar. Якщо такий метод викликається кодом, який негайно примусить результат ввести Foo, використовуючи Barтип повернення, може спрацювати, але той Fooбуде кращим. До речі, я також хотів би побачити засіб, за допомогою якого ...
supercat

... метод міг би визначити, який тип повинен використовуватися в такій конструкції var someVar = someMethod();(або ж призначити, що його повернення не повинно використовуватися таким чином). Наприклад, сімейство типів, що реалізує інтерфейс Fluent, може мати користь від змінних та незмінних версій, тому var thing2 = thing1.WithX(3).WithY(5).WithZ(9);матиме WithX(3)копію thing1на об'єкт, що змінюється, мутує X та повертає цей змінний об'єкт; WithY(5)буде мутувати Y і повертати той самий об'єкт; аналогічно `WithZ (9). Тоді призначення буде перетворене на незмінний тип.
supercat

37

Якщо функції були перевантажені типом повернення, і у вас були ці дві перевантаження

int func();
string func();

компілятор не може зрозуміти, яку з цих двох функцій викликати, побачивши такий виклик

void main() 
{
    func();
}

З цієї причини дизайнери мови часто забороняють перевантажувати значення повернення.

Деякі мови (наприклад, MSIL), проте, дійсно дозволяють перевантажувати повертається типом. Вони теж стикаються з вищезгаданими труднощами, звичайно, але у них є обхідні шляхи, для яких вам доведеться ознайомитися з їх документацією.


4
Незначна каламбур (ваша відповідь дає дуже чітке, зрозуміле обгрунтування): це не те, що немає способу; це просто так, щоб шляхи були незграбними і болючішими, ніж хотіла б більшість людей. Наприклад, у C ++, перевантаження, ймовірно, було б вирішене за допомогою синтаксису некрасивого відтворення.
Майкл Берр

2
@ Йорг W Міттаг: Ви не бачите, що виконують функції. Вони могли легко мати різні побічні ефекти.
А. Рекс

2
@ Jörg - у більшості основних мов програмування (C / C ++, C #, Java та ін.) Функції зазвичай мають побічні ефекти. Насправді я здогадуюсь, що функції з побічними ефектами принаймні такі ж поширені, як і без них.
Майкл Берр

6
Стрибки тут пізно, але в деяких контекстах "функція" має вузьке визначення (по суті) "методу без побічних ефектів". Більш розмовно, "функція" часто використовується взаємозамінно з "методом" або "підпрограмою". Jorg або суворий, або педантичний, залежно від вашої точки зору :)
AwesomeTown

3
Прискочивши ще пізніше, деякі точки зору можуть використовувати прикметники, окрім суворого або педантичного
Патрік Макдональд,

27

Як ви вирішите таке слово мовою:

f(g(x))

якщо fу перевантажень void f(int)і void f(string)і gмали перевантаження int g(int)і string g(int)? Вам знадобиться якийсь розбірник.

Я думаю, що ситуаціям, коли вам це може знадобитися, було б краще служити, вибравши нову назву функції.


2
Регулярний вид перевантаження також може спричинити за собою двозначності. Я думаю, що це зазвичай вирішується шляхом підрахунку кількості необхідних кастингу, але це не завжди працює.
Джей Конрод

1
так, стандартні конверсії класифікуються на точну відповідність, просування та конверсію: void f (int); пустота f (довга); f ('a'); викликає f (int), тому що це лише просування, а перехід до довгого - це перетворення. void f (float); пустота f (коротка); f (10); вимагає конверсії для обох: виклик неоднозначний.
Йоханнес Шауб - ліб

Якщо мова має ледачу оцінку, це не стільки проблема.
jdd

Оновлення, взаємодія перевантаження типів параметрів і перевантаження типу повернення не розглядаються в пості Rex. Дуже хороший момент.
Джозеф Гарвін

1
Якби я проектував мову, моє правило було б те, що для будь-якої перевантаженої функції кожен підпис параметра повинен мати один тип повернення, позначений як за замовчуванням; компілятор розпочнеться з того, що кожен виклик функції використовує тип за замовчуванням. Однак, як тільки це було зроблено, у будь-якій ситуації, коли значення повернення функції було негайно передано або примусоване до чогось іншого, компілятор перевірив би на перевантаження, чиє підпис параметра є ідентичним, але чий тип повернення є кращим відповідним (або можливо недійсним) . Можливо, я б також наклав правило "переохолоджувати - переохочувати все" для таких перевантажень.
supercat

19

Щоб вкрасти відповідь на C ++ з іншого дуже схожого запитання (дуп?):


Типи повернення функцій не грають у роздільній здатності перевантаження просто тому, що Stroustrup (я вважаю, що за допомогою даних інших архітекторів C ++) хотів, щоб роздільна здатність перевантаження була "незалежною від контексту". Див. 7.4.1 - "Тип перевантаження та повернення" з "Мова програмування на C ++, Третє видання".

Причина полягає в тому, щоб роздільна здатність окремого оператора або функції виклику функції не залежала від контексту.

Вони хотіли, щоб це базувалося лише на тому, як називали перевантаження - а не на тому, як використовувався результат (якщо він взагалі був використаний). Дійсно, багато функцій викликаються без використання результату, або результат буде використовуватися як частина більшого виразу. Один із факторів, який я впевнено ввійшов у дію, коли вони вирішили, це те, що якщо тип повернення є частиною резолюції, було б багато викликів перевантаженим функціям, які потрібно було б вирішити за допомогою складних правил, або потрібно було б кинути компілятор помилка, що дзвінок був неоднозначним.

І, Господь знає, роздільна здатність перевантаження C ++ є досить складною, наскільки вона є ...


5

У haskell це можливо, навіть якщо він не має функції перевантаження. Haskell використовує класи типів. У програмі ви могли бачити:

class Example a where
    example :: Integer -> a

instance Example Integer where  -- example is now implemented for Integer
    example :: Integer -> Integer
    example i = i * 10

Сама перевантаження функцій не така популярна. Більшість мов, які я бачив з нею, - це C ++, можливо, java та / або C #. На всіх динамічних мовах це скорочення:

define example:i
  ↑i type route:
    Integer = [↑i & 0xff]
    String = [↑i upper]


def example(i):
    if isinstance(i, int):
        return i & 0xff
    elif isinstance(i, str):
        return i.upper()

Тому в цьому немає особливого сенсу. Більшість людей не цікавить, чи може мова допомогти вам скинути один рядок у будь-якому місці, де ви його використовуєте.

Зіставлення шаблонів дещо схоже на перевантаження функцій, і я думаю, іноді працює аналогічно. Це не звичайно, оскільки корисно лише для декількох програм і складно реалізувати на більшості мов.

Ви бачите, що існує нескінченна кількість інших, легших у виконанні функцій, які можна реалізувати на мові, включаючи:

  • Динамічне введення тексту
  • Внутрішня підтримка списків, словників та рядків унікоду
  • Оптимізація (JIT, тип інтерес, компіляція)
  • Інтегровані засоби розгортання
  • Бібліотечна підтримка
  • Громадська підтримка та місця збору
  • Багаті стандартні бібліотеки
  • Хороший синтаксис
  • Прочитайте цикл друку eval
  • Підтримка рефлексивного програмування

3
У Haskell перевантаження. Класи типу - це мовна функція, яка використовується для визначення перевантажених функцій.
Лій

2

Гарні відповіді! Відповідь А.Рекса, зокрема, дуже детальна та повчальна. Як він вказує, C ++ дійсно вважають введені користувачем оператори перетворення типів при компіляції lhs = func(); (де FUNC дійсно ім'я структури) . Моє вирішення дещо інше - не краще, просто інше (хоча воно засноване на одній і тій же базовій ідеї).

Тоді як я хотів писати ...

template <typename T> inline T func() { abort(); return T(); }

template <> inline int func()
{ <<special code for int>> }

template <> inline double func()
{ <<special code for double>> }

.. etc, then ..

int x = func(); // ambiguous!
int x = func<int>(); // *also* ambiguous!?  you're just being difficult, g++!

Я закінчив рішення, яке використовує параметризовану структуру (з T = тип повернення):

template <typename T>
struct func
{
    operator T()
    { abort(); return T(); } 
};

// explicit specializations for supported types
// (any code that includes this header can add more!)

template <> inline
func<int>::operator int()
{ <<special code for int>> }

template <> inline
func<double>::operator double()
{ <<special code for double>> }

.. etc, then ..

int x = func<int>(); // this is OK!
double d = func<double>(); // also OK :)

Перевага цього рішення полягає в тому, що будь-який код, який включає ці визначення шаблонів, може додати більше спеціалізацій для більшої кількості типів. Також ви можете робити часткові спеціалізації структури за потребою. Наприклад, якщо вам потрібна спеціальна обробка для типів вказівника:

template <typename T>
struct func<T*>
{
    operator T*()
    { <<special handling for T*>> } 
};

Як мінус, ви не можете написати int x = func();моє рішення. Треба писати int x = func<int>();. Ви повинні чітко сказати, що таке тип повернення, а не просити компілятор здавати його на огляд, дивлячись на оператори перетворення типів. Я б сказав, що "моє" рішення і обидва A.Rex належать до парето-оптимального фронту способів вирішити цю дилему C ++ :)


1

якщо ви хочете перевантажувати методи з різними типами повернення, просто додайте фіктивний параметр із значенням за замовчуванням, щоб дозволити виконання перевантаження, але не забувайте, що тип параметра повинен бути іншим, тому наступна логіка перевантаження працює, наприклад, на delphi:

type    
    myclass = class
    public
      function Funct1(dummy: string = EmptyStr): String; overload;
      function Funct1(dummy: Integer = -1): Integer; overload;
    end;

використовувати його так

procedure tester;
var yourobject : myclass;
  iValue: integer;
  sValue: string;
begin
  yourobject:= myclass.create;
  iValue:= yourobject.Funct1(); //this will call the func with integer result
  sValue:= yourobject.Funct1(); //this will call the func with string result
end;

Це жахлива ідея. Не вводьте фіктивних параметрів, це великий запах коду. Натомість вибирайте різні імена, або вибирайте тип повернення, який може діяти так, або є дискримінаційним союзом чи іншим.
Абель

@Abel, що ви пропонуєте, насправді є жахливою ідеєю, оскільки вся ідея стосується цього манекенного параметра, і він називається таким чином, щоб зрозуміти розробнику, що цей параметр є фіктивним і його слід ігнорувати, також у випадку, якщо ви не знаю, що фіктивні параметри зі значеннями за замовчуванням використовуються у багатьох бібліотеках, VCL в delphi та багатьох IDE, наприклад, у delphi ви можете побачити це у підрозділі sysutils у SafeLoadLibrary ...
ZORRO_BLANCO

Звичайно, є сценарії, коли параметри манекена корисні, як, наприклад, в лямбдах при операціях з картою або складанням або при реалізації інтерфейсу. Але просто заради створення перевантаження ні, прошу не погодитися. Немає потреби, і без них шуміти можуть програмісти.
Авель

0

Як уже показано - неоднозначні виклики функції, що відрізняється лише типом повернення, вносять неоднозначність. Неоднозначність викликає несправний код. Необхідно уникати дефектного коду.

Складність, зумовлена ​​спробою неоднозначності, показує, що це не гарний злом. Окрім інтелектуальної вправи - чому б не використовувати процедури з референтними параметрами.

procedure(reference string){};
procedure(reference int){};
string blah;
procedure(blah)

Тому що ви не можете легко використати значення "повернення" негайно. Вам потрібно буде робити кожен дзвінок по одній лінії, на відміну відdoing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
Adowrath

0

цією функцією перевантаження важко керувати, якщо дивитися на це дещо по-іншому. врахуйте наступне,

public Integer | String f(int choice){
if(choice==1){
return new string();
}else{
return new Integer();
}}

якщо мова повертає перевантаження, це дозволить перевантажувати параметри, але не дублювати. це вирішило б проблему:

main (){
f(x)
}

тому що є лише один f (int вибір) на вибір.


0

У .NET, іноді ми використовуємо один параметр, щоб вказати бажаний вихід із загального результату, а потім здійснили перетворення, щоб отримати те, що ми очікуємо.

C #

public enum FooReturnType{
        IntType,
        StringType,
        WeaType
    }

    class Wea { 
        public override string ToString()
        {
            return "Wea class";
        }
    }

    public static object Foo(FooReturnType type){
        object result = null;
        if (type == FooReturnType.IntType) 
        {
            /*Int related actions*/
            result = 1;
        }
        else if (type == FooReturnType.StringType)
        {
            /*String related actions*/
            result = "Some important text";
        }
        else if (type == FooReturnType.WeaType)
        {
            /*Wea related actions*/
            result = new Wea();
        }
        return result;
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Expecting Int from Foo: " + Foo(FooReturnType.IntType));
        Console.WriteLine("Expecting String from Foo: " + Foo(FooReturnType.StringType));
        Console.WriteLine("Expecting Wea from Foo: " + Foo(FooReturnType.WeaType));
        Console.Read();
    }

Можливо, і цей приклад міг би допомогти:

C ++

    #include <iostream>

enum class FooReturnType{ //Only C++11
    IntType,
    StringType,
    WeaType
}_FooReturnType;

class Wea{
public:
    const char* ToString(){
        return "Wea class";
    }
};

void* Foo(FooReturnType type){
    void* result = 0;
    if (type == FooReturnType::IntType) //Only C++11
    {
        /*Int related actions*/
        result = (void*)1;
    }
    else if (type == FooReturnType::StringType) //Only C++11
    {
        /*String related actions*/
        result = (void*)"Some important text";
    }
    else if (type == FooReturnType::WeaType) //Only C++11
    {
        /*Wea related actions*/
        result = (void*)new Wea();
    }
    return result;
}

int main(int argc, char* argv[])
{
    int intReturn = (int)Foo(FooReturnType::IntType);
    const char* stringReturn = (const char*)Foo(FooReturnType::StringType);
    Wea *someWea = static_cast<Wea*>(Foo(FooReturnType::WeaType));
    std::cout << "Expecting Int from Foo: " << intReturn << std::endl;
    std::cout << "Expecting String from Foo: " << stringReturn << std::endl;
    std::cout << "Expecting Wea from Foo: " << someWea->ToString() << std::endl;
    delete someWea; // Don't leak oil!
    return 0;
}

1
Це своєрідне хакерство і може призвести до помилок під час запуску, якщо користувач не подав належним чином результат або якщо розробник не відповідає належним чином типи повернення з enum. Я рекомендую використовувати підхід на основі шаблонів (або загальні параметри в C #?), Такі як у цій відповіді
sleblanc


0

Це трохи відрізняється для C ++; Я не знаю, чи вважатиметься це перевантаження безпосередньо типом повернення. Це більше шаблонна спеціалізація, яка діє в образі.

util.h

#ifndef UTIL_H
#define UTIL_H

#include <string>
#include <sstream>
#include <algorithm>

class util {
public: 
    static int      convertToInt( const std::string& str );
    static unsigned convertToUnsigned( const std::string& str );
    static float    convertToFloat( const std::string& str );
    static double   convertToDouble( const std::string& str );

private:
    util();
    util( const util& c );
    util& operator=( const util& c );

    template<typename T>
    static bool stringToValue( const std::string& str, T* pVal, unsigned numValues );

    template<typename T>
    static T getValue( const std::string& str, std::size_t& remainder );
};

#include "util.inl"

#endif UTIL_H

util.inl

template<typename T>
static bool util::stringToValue( const std::string& str, T* pValue, unsigned numValues ) {
    int numCommas = std::count(str.begin(), str.end(), ',');
    if (numCommas != numValues - 1) {
        return false;
    }

    std::size_t remainder;
    pValue[0] = getValue<T>(str, remainder);

    if (numValues == 1) {
        if (str.size() != remainder) {
            return false;
        }
    }
    else {
        std::size_t offset = remainder;
        if (str.at(offset) != ',') {
            return false;
        }

        unsigned lastIdx = numValues - 1;
        for (unsigned u = 1; u < numValues; ++u) {
            pValue[u] = getValue<T>(str.substr(++offset), remainder);
            offset += remainder;
            if ((u < lastIdx && str.at(offset) != ',') ||
                (u == lastIdx && offset != str.size()))
            {
                return false;
            }
        }
    }
    return true;    
}

util.cpp

#include "util.h"

template<>
int util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stoi( str, &remainder );
} 

template<>
unsigned util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stoul( str, &remainder );
}

template<>
float util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stof( str, &remainder );
}     

template<>   
double util::getValue( const std::string& str, std::size_t& remainder ) {
    return std::stod( str, &remainder );
}

int util::convertToInt( const std::string& str ) {
    int i = 0;
    if ( !stringToValue( str, &i, 1 ) ) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to int";
        throw strStream.str();
    }
    return i;
}

unsigned util::convertToUnsigned( const std::string& str ) {
    unsigned u = 0;
    if ( !stringToValue( str, &u, 1 ) ) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to unsigned";
        throw strStream.str();
    }
    return u;
}     

float util::convertToFloat(const std::string& str) {
    float f = 0;
    if (!stringToValue(str, &f, 1)) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to float";
        throw strStream.str();
    }
    return f;
}

double util::convertToDouble(const std::string& str) {
    float d = 0;
    if (!stringToValue(str, &d, 1)) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to double";
        throw strStream.str();
    }
    return d;
}

У цьому прикладі точно не використовується роздільна здатність перевантаження функцій за типом повернення, однак цей не + об'єктний клас c ++ використовує спеціалізацію шаблонів для моделювання роздільної здатності перевантаження функцій за типом повернення приватним статичним методом.

Кожна з convertToTypeфункцій викликає шаблон функції, stringToValue()і якщо ви подивитесь на деталі реалізації або алгоритм цього шаблону функції, він викликає, getValue<T>( param, param )і він повертає назад тип Tі зберігає його у форматі, T*який передається вstringToValue() шаблон функції як один із його параметрів. .

Окрім чогось подібного; C ++ насправді не має механізму функціонування роздільної здатності перевантаження за типом повернення. Можливо, існують інші конструкції чи механізми, про які я не знаю, які могли б імітувати вирішення за типом повернення.


-1

Я думаю, що це GAP в сучасному визначенні C ++ ... чому?

int func();
double func();

// example 1. → defined
int i = func();

// example 2. → defined
double d = func();

// example 3. → NOT defined. error
void main() 
{
    func();
}

Чому компілятор C ++ не може навести помилку в прикладі "3" та прийняти код у прикладі "1 + 2" ??


Так, це те, що вони розглядали в той час для C # (і, можливо, C ++). Але хоча ваш код тривіальний, як тільки ви додаєте ієрархії класів, віртуальні методи, конспекти та інтерфейси, інші перевантаження та, іноді багаторазове успадкування, дуже швидко стає дуже складним, щоб вирішити, який метод слід вирішити. Це вибір дизайнерів не йти цим маршрутом, але інші мови вирішили по-різному на різних рівнях успіху.
Абель

-2

Більшість статичних мов також тепер підтримують дженерики, які вирішили б вашу проблему. Як було сказано раніше, не маючи параметра, який відрізняється, немає способу дізнатися, до якого дзвонити. Тож якщо ви хочете це зробити, просто використовуйте дженерики і називайте це на день.


Не те саме. Як би ви поводилися з функцією, яка переводить вхід на ціле число, float, bool або будь-що інше, виходячи з того, як використовується тип повернення? Це неможливо узагальнити, оскільки для кожного вам потрібен спеціальний випадок.
Джей Конрод

Дивіться codeproject.com/KB/cpp/returnoverload.aspx для розумної стратегії "перевантаження при поверненні типу". В основному, замість того, щоб визначати функцію func (), ви визначаєте структуру func, даєте їй оператор () () та перетворення для кожного відповідного типу.
j_random_hacker

Джей, ти визначаєш тип повернення, коли викликаєш функцію. Якщо впускні речовини відрізняються, то проблем взагалі немає. Якщо такі самі, ви можете мати загальну версію, яка може мати певну логіку на основі типу за допомогою GetType ().
Чарльз Грем
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.