当尝试使用 decltype 生成指向重载函数的函数指针时,为什么无法指定适当的重载?

Jus*_*ica 1 c++ function-pointers decltype c++11

我正在尝试函子,无意中发现了一些decltype我觉得有趣的事情:据我所知,没有办法使用它来生成指向重载函数的函数指针,或为其提供解决所述重载所需的信息当尝试这样做时。

// Simple test code.
// Please ignore the possibility of attempting to dereference a nullptr function pointer,
//  proper checking was omitted for brevity.
template<typename... Ts> struct Functor;

template<typename F, typename R, typename... Ts>
struct Functor<F, R(*)(Ts...)> {
    Functor() : func(nullptr) { }
    Functor(F f) : func(f) { }

    R operator()(F f, Ts... ts) { return 2 * f(ts...); }
    R operator()(Ts... ts) { return 2 * func(ts...); }

    private:
        F func;
};

template<typename F>
decltype(auto) functorReturner(F f = nullptr) {
    return ((f) ? Functor<F, F>{f} : Functor<F, F>{});
}

int example(int a, int b, int c) {
    return (8 * a) * ((b ^ c) - (c ^ b));
}

float example(float a, float b, float c) {
    return (4.2 / a) * ((b + c) * (c - b));
}

// Wrappers to avoid overload when using decltype.
auto example_i(int a, int b, int c) { return example(a, b, c); }
auto example_f(float a, float b, float c) { return example(a, b, c); }

int main() {
  // Valid:
    // Automatic template deduction, no overloads.
    std::cout << functorReturner(example_f)(1., 2.8, 0.6) << std::endl; // Outputs -62.832.
    // Explicit template specification, no overloads.
    std::cout << functorReturner<decltype(&example_f)>()(example_f, 1., 2.8, 0.6)
              << std::endl;
    // Explicit template specification, determines correct overload.
    std::cout << functorReturner<decltype(&example_f)>(example)(1., 2.8, 0.6)
              << std::endl;
    // Explicit template specification, determines correct overload.
    std::cout << functorReturner<decltype(&example_f)>()(example, 1., 2.8, 0.6)
              << std::endl;
  // Invalid:
    // Attempting to pass proper version of overloaded function to decltype, without using
    // a non-overloaded function with an identical signature.
    // Explicit template specification, could theoretically determine correct overload.
    std::cout << functorReturner<decltype(&example(float, float, float))>(example)(1., 2.8, 0.6)
              << std::endl;

  // Valid:
    // Determine type from non-overloaded function.
    decltype(example_f(1., 2.8, 0.6)) ex;

    // Determine type from overloaded function, with arguments explicitly specified.
    decltype(example(1.f, 2.8f, 0.6f)) ex2;

    // Determine type from overloaded function, with Rvalues to aid in resolution.
    decltype(example(float{}, float{}, float{})) ex3;

    std::cout << typeid(ex).name() << " "
              << typeid(ex2).name() << " "
              << typeid(ex3).name()
              << std::endl;
    // Output is:
    //  MSVC: "float float float"
    //  GCC: "f f f"
    // Return type deduced correctly in all three examples, even when overload resolution
    // is required.

  // Valid:
    // Determine function type via decltype.
    // Automatically resolves overload, as only one version of example() is the same type
    // as example_f():
    // "float (*)(float, float, float)".
    decltype(&example_f) exfp = example;
    std::cout << (2 * example(1., 2.8, 0.6)) << std::endl;
  // Invalid:
    // Explicitly specify which version of overloaded function we mean.
    decltype(&example(float, float, float)) exfp_inv = example;
    // or...
    decltype(&example(1.f, 2.8f, 0.6f) exfp_inv2 = example;
    // or...
    decltype(&example(float{}, float{}, float{})) exfp_inv3 = example;
}
Run Code Online (Sandbox Code Playgroud)

考虑到上述情况,我的问题是:在使用 using 获取重载函数的签名的情况下decltype,为什么不能指定函数参数或至少其类型以允许重载解析?

根据标准,引用cppreference

1) 如果参数是不带括号的 id 表达式或不带括号的类成员访问,则 decltype 生成由此表达式命名的实体的类型。如果没有这样的实体,或者参数指定了一组重载函数,则程序的格式不正确。

我觉得在这种情况下,参数命名了一组重载函数,允许程序员指定参数类型的语法将允许程序正确形成,即使在decltype与重载函数一起使用时也是如此,我真的很好奇为什么会这样是不允许的。我不认为它有多大用处(使用模板创建有效的函数指针或删除一个来确定返回和参数类型很容易,这将考虑重载并允许与能够实现的功能基本相同的功能)直接在重载函数上使用;在您无法decltype轻松确定所述信息的情况下,某些内容可能编码得不好,从而使这一点变得毫无意义),但由于它应该很容易实现,我很惊讶事实并非如此。

一个可能的实现:

// Currently legal syntax, which will be used as a basis.
decltype(example(1.f, 2.8f, 0.6f)) e;
// decltype properly deduces that we want the return type of
//  "float example(float, float, float)".

// Possible implementation, based on the above.
decltype(&example(1.f, 2.8f, 0.6f)) ep;
// decltype is currently unable to deduce that we want a function pointer with a signature of
//  "float example(float, float, float)".
// However, this syntax would allow it to deduce this if the standard allowed it.
Run Code Online (Sandbox Code Playgroud)

...如果有一种方法可以decltype在创建我不知道的函数指针时正确解决重载,那么我也会有兴趣了解它,因为我的好奇心被激发了。它被省略了,这似乎很奇怪。

[如果这是重复的,我很抱歉。我见过的大多数类似问题似乎都是关于如何解决这个问题,而我的问题则是关于其背后的逻辑。]


编辑:为了澄清,问题是这样的:对于任何给定的重载函数,为什么能够decltype(function_name(appropriate_parameters))成功地将重载解析为函数的正确版本并评估其返回类型,而 whiledecltype(&function_name(appropriate_parameters))或类似的语法无法成功解析重载到函数的正确版本并评估其签名?这对我来说似乎不一致,特别是因为从理论上讲,实施起来很简单。

int example(int, int, int)float example(float, float, float)为例,如果decltype(example(1.f, 2.8f, 0.6f))能够成功解析 重载float example(float, float, float)并求值为float,为什么decltype(&example(1.f, 2.8f, 0.6f))无法成功解析重载float example(float, float, float)并求值为float (*)(float, float, float)

Bar*_*rry 5

如果您查看 [over.over],您会发现在一些特定的上下文中您可以使用重载函数的名称/地址:

\n\n
\n

目标可以是
\n \xe2\x80\x94(1.1) 正在初始化的对象或引用 (8.5、8.5.3、8.5.4)、
\n \xe2\x80\x94(1.2) 赋值的左侧(5.18),
\n \xe2\x80\x94(1.3) 函数的参数 (5.2.2),
\n \xe2\x80\x94(1.4) 用户定义的\xef\xac\x81ned 的参数运算符 (13.5)、
\n \xe2\x80\x94(1.5) 函数、运算符函数或转换的返回值 (6.6.3)、
\n \xe2\x80\x94(1.6) 显式类型转换 ( 5.2.3、5.2.9、5.4) 或
\n \xe2\x80\x94(1.7) 一个非类型模板参数 (14.3.2)。

\n
\n\n

通俗地说,您可以example在恰好存在一个“适合”的重载的上下文中使用。你不能这样做是decltype(example)有道理的 - 没有一种独特的例子。但您可能正在寻找的是倒数第二个选项:您始终可以将重载函数转换为特定类型:

\n\n
auto example_i = static_cast<int(*)(int, int, int)>(example);\nauto example_f = static_cast<float(*)(float, float, float)>(example);\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者变量赋值:

\n\n
template <class T>\nusing F3 = T(*)(T, T, T);\n\nF3<int> example_i = example;\nF3<float> example_f = example;\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者非类型模板参数:

\n\n
template <class T, F3<T> Func>\nstruct Foo { ... };\n\nFoo<int, example> f;\n
Run Code Online (Sandbox Code Playgroud)\n\n

ETC。

\n