【问题标题】:Type deduction fails with pointer to member method类型推导失败并带有指向成员方法的指针
【发布时间】:2013-05-28 14:28:13
【问题描述】:

我有以下充当代理的模板类。它有一个名为call 的方法,它应该用于调用包装对象上的方法。它有问题。类型推导失败,我不明白为什么。

Hudsucker::f 接受std::string,然后无论我传递std::string 还是const 引用,编译器都能够调用正确的方法。

但在Hudsucker::g 的情况下,使用const 引用std::string 类型推导在GCC 和Clang 的两种情况下都失败。

第一行的 GCC 错误:

main.cpp:36:28: error: no matching function for call to ‘Proxy<Hudsucker>::call(void (Hudsucker::*)(const string&), const string&)’
main.cpp:36:28: note: candidate is:
main.cpp:10:10: note: template<class A> void Proxy::call(void (T::*)(A), A) [with A = A; T = Hudsucker]
main.cpp:10:10: note:   template argument deduction/substitution failed:
main.cpp:36:28: note:   deduced conflicting types for parameter ‘A’ (‘const std::basic_string<char>&’ and ‘std::basic_string<char>’)

特别是这个位很奇怪:no matching function for call to Proxy&lt;Hudsucker&gt;::call(void (Hudsucker::*)(const string&amp;), const string&amp;)。这正是我希望看到的签名。

第一行的 Clang 错误:

main.cpp:36:7: error: no matching member function for call to 'call'
    p.call(&Hudsucker::g, s); // <- Compile error
    ~~^~~~
main.cpp:10:10: note: candidate template ignored: deduced conflicting types for parameter 'A' ('const std::basic_string<char> &' vs. 'std::basic_string<char>')
    void call(void (T::*f)(A), A a)

代码:

#include <string>
#include <iostream>

template <typename T> class Proxy
{
public:
    Proxy(T &o): o_(o) {}

    template <typename A>
    void call(void (T::*f)(A), A a)
    {
        (o_.*f)(a);
    }

private:
    T &o_;
};

class Hudsucker
{
public:
    void f(std::string s) {}
    void g(std::string const &s) {}
};

int main()
{
    Hudsucker h;
    Proxy<Hudsucker> p(h);
    std::string const s = "For kids, you know.";
    std::string const &r = s;

    p.call(&Hudsucker::f, s);
    p.call(&Hudsucker::f, r);

    p.call(&Hudsucker::g, s); // <- Compile error
    p.call(&Hudsucker::g, r); // <- Compile error

    return 0;
}

你能解释一下为什么类型推导会以这种方式失败吗?有没有办法用const 引用来编译它?

【问题讨论】:

    标签: c++ templates type-deduction


    【解决方案1】:

    编译器无法推断出类型A,因为它具有对比信息。从成员函数的类型推导出Astd::string const&amp;,而从第二个参数的类型推导为std::string

    将您的函数模板更改为允许成员函数的参数和实际提供的参数的不同类型的模板,然后 SFINAE-constrain 后者可转换为前者:

    template <typename A, typename B,
        typename std::enable_if<std::is_convertible<B, A>::value>::type* = nullptr>
    void call(void (T::*f)(A), B a)
    {
        (o_.*f)(a);
    }
    

    如果你想知道为什么从这个函数调用:

    std::string const s = "For kids, you know.";
    // ...
    p.call(&Hudsucker::g, s);
    

    编译器会推断出std::string,这是因为 C++11 标准的第 14.8.2.1/2 段:

    如果P 不是引用类型

    ——如果A是数组类型,则数组到指针标准转换(4.2)产生的指针类型是 用于代替A进行类型推导;否则,

    ——如果A是函数类型,则函数到指针标准转换(4.3)产生的指针类型 用于代替A进行类型推导;否则,

    如果 A 是 cv 限定类型,则在类型推导中忽略 A 类型的顶级 cv 限定符

    在引用的段落中,P 是您的 A(来自您的函数模板),Astd::string const。这意味着std::string const 中的const 被忽略以进行类型推断。为了更好地了解这一点,请考虑以下更简单的示例:

    #include <type_traits>
    
    template<typename T>
    void foo(T t)
    {
        // Does NOT fire!
        static_assert(std::is_same<T, int>::value, "!");
    }
    
    int main()
    {
        int const x = 42;
        foo(x);
    }
    

    考虑第二个函数调用:

    std::string const &r = s;
    // ...
    p.call(&Hudsucker::g, r);
    

    原因是id-expression r 的类型是std::string const。由于第 5/5 段,引用被删除:

    如果表达式最初的类型为“引用T”(8.3.2、8.5.3),则该类型在之前调整为T 任何进一步的分析。表达式指定引用表示的对象或函数,而 表达式是左值还是 xvalue,具体取决于表达式。

    现在我们又回到了第一次函数调用的情况。


    正如 Mike Vine 在 cmets 中指出的那样,在函数调用期间将第二个参数作为第一个(成员函数)参数的输入时,您可能希望完美地转发第二个参数: p>

    #include <utility> // For std::forward<>()
    
    template <typename A, typename B,
        typename std::enable_if<std::is_convertible<B, A>::value>::type* = nullptr>
    void call(void (T::*f)(A), B&& a)
    {
        (o_.*f)(std::forward<B>(a));
    }
    

    如果你买不起 C++11,那么你将不能使用模板参数的默认参数。在这种情况下,您可以在返回类型上使用 SFINAE 约束:

    template <typename A, typename B>
    typename enable_if<is_convertible<B, A>::value>::type 
    //       ^^^^^^^^^ ^^^^^^^^^^^^^^
    //       But how about these traits?
        call(void (T::*f)(A), B a)
    {
        (o_.*f)(a);
    }
    

    请注意,std::enable_ifstd::is_convertible 不是 C++03 标准库的一部分。幸运的是,Boost 有自己的enable_ifis_convertible 版本,所以:

    #include <boost/utility/enable_if.hpp>
    #include <boost/type_traits/is_convertible.hpp>
    
    template <typename T> class Proxy
    {
    public:
        Proxy(T &o): o_(o) {}
    
        template <typename A, typename B>
        typename boost::enable_if<boost::is_convertible<B, A>>::type 
            call(void (T::*f)(A), B a)
        {
            (o_.*f)(a);
        }
    
    private:
        T &o_;
    };
    

    注意,boost::enable_if 接受一个定义value 布尔成员的type 作为其第一个模板参数,而std::enable_if 接受一个布尔值。 Boost 中std::enable_if 的等价物是boost::enable_if_c

    【讨论】:

    • 无论如何我都不是这里的专家,但你不想在这里使用完美转发。
    • @MikeVine:是的,这确实是合适的,但在这个答案中,我想提供一个更改最少的工作解决方案,以便 OP 可以理解他的原始程序出了什么问题(完美转发是一种改进,但本身并不能解决问题)。无论如何,我可能应该将此作为注释添加,谢谢!
    • 另外我很确定你想要::type* = nullptr之前
    • @AndyProwl,我已经看到并删除了评论=)GCC给出的错误no matching function for call to Proxy&lt;Hudsucker&gt;::call(void (Hudsucker::*)(const string&amp;), const string&amp;)。它在两个地方都显示const string&amp;。奇怪。
    • @detunized:我相信 GCC 给你的诊断是错误的(尽管拒绝来电是正确的)。 Clang 正在给你我期望的错误消息
    【解决方案2】:

    在我看来,一个更简单的解决方案是仅排除两个参数之一来尝试推断 A,而第二个参数是更好的候选者:

    template <typename A>
    void call(void (T::*f)(A), typename std::identity<A>::type a)
    {
        (o_.*f)(a);
    }
    

    如果你的类型特征中没有std::identity,请使用这个:

    template <typename T>
    struct identity { typedef T type; };
    

    这就是为什么这样做的原因:编译器不能从第二个参数中推断出 A,因为它只是一个模板参数,用于采用嵌套类型的东西。基本上,它不能将任何传入类型与 something_that_contains_A::type 进行模式匹配——由于模板专业化,它不能从左侧的定义中对参数进行逆向工程。最终结果是第二个参数是“未推断的上下文”。编译器不会尝试从那里推断出 A。

    这使得第一个参数成为唯一可以从中推断出 A 的地方。 A只有一个扣分结果,不模棱两可,扣分成功。然后编译器继续将推导结果替换到使用 A 的每个地方,包括第二个参数。

    【讨论】:

    • 这实际上适用于 C++03 的几个简单示例。我将不得不看看是否有任何奇怪的角落案例失败。谢谢!
    • 顺便说一句,没有标准的 std::identity 类型特征 - 但可以将 std::common_type 用作 std::identity (或者像你一样定义自己的特征)
    • 现在我需要了解它为什么有效。你能解释一下为什么这样的技巧有帮助吗?
    • 感谢您提供有效的解决方案。但我接受了另一个答案,因为它解释了为什么会这样。这是我最感兴趣的。
    【解决方案3】:

    您只需在 main 中调用模板函数时将模板参数传递给模板函数。

    int main()
    {
        Hudsucker h;
        Proxy<Hudsucker> p(h);
        std::string const s = "For kids, you know.";
        std::string const &r = s;
    
        p.call(&Hudsucker::f, s);
        p.call(&Hudsucker::f, r);
    
        //just add template argument to template function call !!!
        p.call< const std::string & > (&Hudsucker::g, s); // <- NO  Compile error !!!!
        p.call< const std::string & > (&Hudsucker::g, r); // <- NO Compile error !!!**
    
       return 0;
    

    }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-03-24
      • 1970-01-01
      • 2021-12-18
      • 1970-01-01
      • 2020-04-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多