简体   繁体   English

用模板向前声明所有将来的函数会导致模棱两可,而不是将声明与定义配对

[英]Forward declaring all future functions with a template results in ambiguity instead of pairing declaration with definition

I'm using a c++ idiom that allows defining a common behavior ( GeneralFunction ) that is customized by making it call specific functions ( SpecificFunction ) – switching between them by tag classes (unfortunately I forgot the name of the idiom). 我正在使用一个c ++惯用法,该惯用法允许定义一个通用行为( GeneralFunction ),该行为通过使其调用特定函数( SpecificFunction )进行自定义-通过标记类在它们之间切换(不幸的是,我忘记了该惯用语的名称)。

I decided I wanted the SpecificFunctions to have an arbitrary return type. 我决定要让SpecificFunctions具有任意返回类型。 But since we're in c++ I need to declare the identifier in advance before mentioning it in GeneralFunction . 但是由于我们使用的是c ++,因此我需要在GeneralFunction之前先声明标识符。

Here I hope I'm declaring a SpecificFunction template that will be upon its instantiation a declaration matching a concrete SpecificFunction overload which will be defined below: 我希望在这里声明一个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

the return type is not known until instantiation, but it should not matter now. 返回类型在实例化之前是未知的,但是现在不重要了。 I just need to to declare the identifier, so that I can use it in GeneralFunction 我只需要声明标识符,以便可以在GeneralFunction使用它

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

The general function with common behavior: 具有常见行为的一般功能:

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) ...);
}

Default implementation for SpecificFunction I decided to provide: 我决定提供的SpecificFunction的默认实现:

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

defining specific behavior functions: 定义特定的行为功能:

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;
}

Error message: 错误信息:

/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

It seems it cannot match the generated declaration to the definition of the default SpecificFunction implementation. 似乎无法将生成的声明与默认的SpecificFunction实现的定义匹配。

You can see the generated declaration is the same (they call it error: ambiguous) as the definition header. 您可以看到生成的声明与定义头相同(他们称其错误:不明确)。 That's funny, because that's exactly the thing – that identity – based on which I thought the declaration and definition was paired. 这很有趣,因为那正是身份(我认为声明和定义已配对)的基础。

Also I tried changing the header of // default implementation SpecificFunction to 我也尝试将// default implementation SpecificFunction的标头更改为

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

but then I get a linker error instead: 但是然后我得到一个链接器错误:

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)

First note that the return type is a part of the function signature of a function template specialization. 首先请注意,返回类型是功能模板专业化功能签名的一部分。

Thus, the compiler sees two overloads that match Tag = Alogirthm3 , which (may) differ in their return types: 因此,编译器会看到两个与Tag = Alogirthm3匹配的重载,它们的返回类型可能不同:

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

and

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

To figure out that they are in fact the same, it has to figure out what is typename ReturnTypeTrick<Tag, Args...>::type , which is defined as decltype(SpecificFunction(Tag(), std::declval<Args>() ...) , once again leading to the resolution of the SpecificFunction overload for Tag = Algorithm3 (which was the problem that we started with). So, what the code is telling the compiler is: 要弄清楚它们实际上是相同的,它必须弄清楚什么是typename ReturnTypeTrick<Tag, Args...>::type ,其定义为decltype(SpecificFunction(Tag(), std::declval<Args>() ...) ,再次导致Tag = Algorithm3SpecificFunction重载的解析(这是我们开始的问题)。因此,代码告诉编译器的是:

(A) and (B) are the same if and only if (A) and (B) are the same. 当且仅当(A)和(B)相同时,(A)和(B)相同。

which is obviously ambiguous. 这显然是模棱两可的。 Thus, your return type trick is actually incorrect. 因此,您的返回类型技巧实际上是不正确的。

The question is: why do you even need (A) and ReturnTypeTrick in the first place? 问题是:为什么您首先甚至需要(A)和ReturnTypeTrick Because of the way template instantiation works (in short, specializations are instantiated lazily, just before the place where they are first needed), you should not need the forward declaration at all. 由于模板实例化的工作方式(简而言之,专业化被懒惰地实例化,就在首次需要它们的地方之前),您根本不需要前向声明。 Just remove ReturnTypeTrick and (A) altogether and your code will compile. 只需完全删除ReturnTypeTrick和(A),即可编译您的代码。

See live example . 参见现场示例 Also, the discussion in this question might be an interesting read. 另外,关于这个问题的讨论可能会很有趣。 The setting is not exactly the same as yours, but it does discuss the issue of templates behaving differently than normal functions / classes (complete with references to the C++ standard, as opposed to hand waving used in this answer). 设置与您的设置不完全相同,但是确实讨论了模板的行为与常规函数/类(与C ++标准的引用相对应,而不是此答案中所用的手法)的不同。

BTW: In case you were wondering why the specializations for Algorithm1 and Algorithm2 do not have the same problem, it is because a non-template parameter is a better match in overload resolution than a template parameter, so (A) is immediately discarded as a candidate, before even going through the return type resolution. 顺便说一句:如果您想知道为什么Algorithm1Algorithm2的专业化不存在相同的问题,那是因为非模板参数在过载分辨率方面比模板参数更好地匹配,因此(A)立即被丢弃为候选人,甚至还没有通过返回类型解析。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM