Форвардное объявление всех будущих функций с шаблоном приводит к неоднозначности вместо объявления пары с определением - PullRequest
0 голосов
/ 19 ноября 2018

Я использую идиому c ++, который позволяет определить общее поведение (GeneralFunction), которое настраивается путем вызова определенных функций (SpecificFunction) - переключения между ними по классам тегов (к сожалению, я забыл названиеидиома).

Я решил, что хотел, чтобы SpecificFunctions имели произвольный тип возврата.Но так как мы находимся в c ++, мне нужно объявить идентификатор заранее, прежде чем упомянуть его в GeneralFunction.

Здесь я надеюсь, что я объявляю шаблон SpecificFunction, который при его создании будет объявлением, соответствующим конкретной перегрузке SpecificFunction, которая будет определена ниже:

#include <utility>
#include <iostream>

template <typename Tag, typename ... Args>
struct ReturnTypeTrick;

template <typename Tag, typename ... Args>
inline auto SpecificFunction(Tag, Args&& ... args) -> typename ReturnTypeTrick<Tag, Args...>::type;  // basically saying SpecificFunction returns what it returns

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

template <typename Tag, typename ... Args>
struct ReturnTypeTrick {
    using type = decltype(SpecificFunction(Tag(), std::declval<Args>() ...));
};

Общая функция с общим поведением:

template <typename Tag, typename ... Args>
inline auto GeneralFunction (Args&& ... specific_args) {
    // do something common for all implementations

    // this is where the behavior differs
    return SpecificFunction(Tag(), std::forward<Args>(specific_args) ...);
}

Реализация по умолчанию для SpecificFunction Я решилпредоставить:

template <typename Tag, typename ... Args>
inline bool SpecificFunction(Tag, Args&& ...) {  // default implementation
    return false;
}

определение определенных функций поведения:

struct Algorithm1 {};

inline auto SpecificFunction(Algorithm1, int param1, char param2) {
    return 10;
}


struct Algorithm2 {};

inline auto SpecificFunction(Algorithm2, long param1) {
    return "y";
}

struct Algorithm3 {};



int main() {
    std::cout << GeneralFunction<Algorithm1>(1, 'a') << std::endl;
    std::cout << GeneralFunction<Algorithm2>(1l) << std::endl;  
    /* these somehow work, probably independently on the
   `SpecificFunction` declaration above because my compiler is OK with
   functions not being declared at all. BUT changing these simple
   SpecificFunction definitions into templates – that is line in
   GeneralFunction would look like 
   return SpecificFunction<SomeTemplateArg>(Tag(), std::forward<Args>(specific_args) ...); 
   – and removing the declaration results in undeclared indentifier
   `SpecificFunction`, so I need to be able to declare it */

    std::cout << GeneralFunction<Algorithm3>("wtf this should call the  // default implementation") << std::endl;
    // error: call to 'SpecificFunction' is ambiguous

    static_assert(std::is_same_v<typename ReturnTypeTrick<Algorithm3, const char*>::type, bool>, "the return type of the generated declaration is correct if you don't see this message");

    return 0;
}

Сообщение об ошибке:

/scratch_2.cpp:22:12: error: call to 'SpecificFunction' is ambiguous
    return SpecificFunction(Tag(), std::forward<Args>(specific_args) ...);
           ^~~~~~~~~~~~~~~~
/scratch_2.cpp:51:18: note: in instantiation of function template
      specialization 'GeneralFunction<Algorithm3, char const (&)[52]>' requested here
    std::cout << GeneralFunction<Algorithm3>("wtf this should call the  // default implementation") << std::endl;
                 ^
/scratch_2.cpp:8:13: note: candidate function [with Tag = Algorithm3, Args
      = <char const (&)[52]>]
inline auto SpecificFunction(Tag, Args&& ... args) -> typename ReturnTypeTrick<Tag, Args...>::type;  // basically saying Spe...
            ^
/scratch_2.cpp:26:7: note: candidate function [with Tag = Algorithm3, Args
      = <char const (&)[52]>]
inline bool SpecificFunction(Tag, Args&& ...) {  // default implementation

Кажется, оно не может сопоставить сгенерированное объявление с определениемреализация по умолчанию SpecificFunction.

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

Также я попытался изменить заголовок // default implementation SpecificFunction на

template <typename Tag>
inline bool SpecificFunction(Tag, ...) {  // default implementation
    return false;
}

но вместо этого я получаю ошибку компоновщика:

Undefined symbols for architecture x86_64:
  "ReturnTypeTrick<Algorithm3, char const (&) [52]>::type SpecificFunction<Algorithm3, char const (&) [52]>(Algorithm3, char const (&&&) [52])", referenced from:
      auto GeneralFunction<Algorithm3, char const (&) [52]>(char const (&&&) [52]) in scratch_2-b3bb4f.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

1 Ответ

0 голосов
/ 19 ноября 2018

Первое замечание: тип возвращаемого значения является частью сигнатуры функции специализации шаблона функции.

Таким образом, компилятор видит две перегрузки, соответствующие Tag = Alogirthm3, которые (могут) различаться по типам возвращаемых данных:

// (A)
template <typename Tag, typename ... Args>
inline auto SpecificFunction(Tag, Args&& ... args)
    -> typename ReturnTypeTrick<Tag, Args...>::type;

и

// (B)
template <typename Tag, typename ... Args>
inline bool SpecificFunction(Tag, Args&& ...);

Чтобы выяснить, что они на самом деле одинаковы, нужно выяснить, что такое typename ReturnTypeTrick<Tag, Args...>::type, что определяется как decltype(SpecificFunction(Tag(), std::declval<Args>() ...), что снова приводит к разрешению перегрузки SpecificFunction для Tag = Algorithm3 ( с которой мы и начали). Итак, код говорит компилятору:

(A) и (B) одинаковы тогда и только тогда, когда (A) и (B) одинаковы.

что явно неоднозначно. Таким образом, ваш трюк с возвращаемым типом на самом деле неверен.

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

См. живой пример . Кроме того, обсуждение в этого вопроса может быть интересным чтением. Настройка не совсем такая же, как у вас, но она обсуждает проблему шаблонов, которые ведут себя не так, как обычные функции / классы (дополнены ссылками на стандарт C ++, в отличие от размахивания руками, использованного в этом ответе).

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...