3

プロキシとして機能する次のテンプレート クラスがあります。callラップされたオブジェクトでメソッドを呼び出すために使用されることになっているという名前のメソッドがあります。問題があります。型推定が失敗し、その理由がわかりません。

Hudsucker::fを取り、それへの参照std::stringを渡すかどうかに関係なく、コンパイラは正しいメソッドを呼び出すことができます。std::stringconst

しかし、GCC と Clang の両方で型推論へHudsucker::gconst参照が失敗する場合。std::string

最初の行の 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<Hudsucker>::call(void (Hudsucker::*)(const string&), const string&). それはまさに私が期待する署名です。

最初の行の 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ますか?

4

3 に答える 3

12

A対照的な情報があるため、コンパイラは type を推測できません。メンバー関数の型からは であると推測Aされますがstd::string const&、2 番目の引数の型からは であると推測されますstd::string

関数テンプレートを、メンバー関数のパラメーターと実際に提供される引数に異なる型を許可するものに変更し、後者を前者に変換できるように SFINAE 制約します。

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 修飾された型の場合、 の型の最上位の cv 修飾子はA、型推定では無視されます。

引用された段落では、Pis your A(関数テンプレートから) and Aisstd::string constです。これは、型推論ではconstinが無視されることを意味します。std::string 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);
}

2 番目の関数呼び出しを考慮すると、次のようになります。

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その後の分析の前に に調整されます。式は参照によって示されるオブジェクトまたは関数を指定し、式は式に応じて左辺値または x 値です。

これで、最初の関数呼び出しと同じ状況に戻りました。


コメントで Mike Vine が指摘したように、関数呼び出し中に最初の (メンバー関数) 引数への入力として 2 番目の引数を渡すときに、2 番目の引数を完全に転送したい場合があります。

#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_ifandis_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最初のテンプレート引数として、ブール値を受け入れるのに対し、ブール値メンバーを定義するを受け入れることに注意してください。Boost に相当するのは.valuestd::enable_ifstd::enable_ifboost::enable_if_c

于 2013-05-28T14:33:13.210 に答える
1

メインで呼び出すときに、テンプレート引数をテンプレート関数に渡すだけです。

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;

}

于 2014-10-08T18:22:47.393 に答える