意外需要的 C++ 模板专项课程

Unexpectedly required C++ Template Specialization

提问人:duncan 提问时间:9/27/2023 最后编辑:Wyckduncan 更新时间:9/27/2023 访问量:111

问:

我正在尝试为对象容器实现一个通用的 setter。Foo

我不明白为什么 clang 抱怨这段代码,因为:

  1. 您可以完全将 or 文本传递给作为参数的函数std::stringconst std::string &

  2. 模板专用化(确实会编译并具有预期行为)具有与模板化版本完全相同的代码。

知道我做错了什么吗?

谢谢。

#include <iostream>
#include <string>
#include <vector>

class Foo
{
  protected:
    int _x;
    std::string _name;

  public:
    void set_x(int x) { _x = x; }
    void set_name(const std::string & name) { _name = name; }

    int x() const { return _x; }
    std::string name() const { return _name; }
};

template <typename T, typename Container>
void set_property(Container & container, const T & value, void (Foo::*setter)(T))
{
    for (auto & element : container)
        (element.*setter)(value);
}

// Uncomment this to succeed. 
//
//template <typename Container>
//void set_property(Container & container, const std::string & value, void (Foo::*setter)(const //std::string &))
//{
//    for (auto & element : container)
//        (element.*setter)(value);
//}

int main(void)
{
    int x = 42;
    std::string name = "foo";

    // Initialization.
    std::vector<Foo> foos(10);
    for (auto & foo : foos)
    {
        foo.set_x(x);
        foo.set_name(name);
    }

    x = 17;
    name = "bar";

    // Set properties.
    set_property(foos, x, &Foo::set_x);
    set_property(foos, name, &Foo::set_name);

    std::cout << "x: " << foos[0].x() << std::endl;
    std::cout << "name: " << foos[0].name() << std::endl;

    return 0;
}

咔嚓咔嚓错误:

❯❯  g++ --std=c++20 foo.cpp &&./a.out
foo.cpp:44:5: error: no matching function for call to 'set_property'
    set_property(foos, name, &Foo::set_name);
    ^~~~~~~~~~~~
foo.cpp:20:6: note: candidate template ignored: deduced conflicting types for parameter 'T' ('std::string' (aka 'basic_string<char>') vs. 'const std::string &' (aka 'const basic_string<char> &'))
void set_property(Container & container, const T & value, void (Foo::*setter)(T))
     ^
1 error generated.
C++ C++20 模板专业化

评论

2赞 BoP 9/27/2023
编译器告诉你问题:对于第二个参数必须是 ,但对于 setter 必须是 。Tstd::stringTconst std::string&
0赞 duncan 9/27/2023
是的。。。这就是我的观点。它怎么会抱怨?原则上,我应该能够将 a 传递给接受 的函数,就像将 传递给接受 的函数完全相同stringconst std::string &intconst int &

答:

0赞 n. m. could be an AI 9/27/2023 #1

如果多次推导模板参数,则该参数必须在整个集合中完全匹配。

你有:

template <typename T, typename Container>
void set_property(Container & container, const T & value, void (Foo::*setter)(T))

请注意如何在两个单独的函数参数中使用,以及 。Tvaluesetter

现在,当您致电时

set_property(foos, name, &Foo::set_name);

T推导了两次,分别从 和 中推导出。两者应该完全相同。但他们不是,说它一定是,而说它一定是.nameset_nameTnamestd::stringset_nameconst std::string&

你说“哦,但可以在哪里使用”。这是真的,但无关紧要。两者必须相同。几乎不一样,在某种程度上不兼容,但相同。std::string&const std::string&T

如果你取消注释硬编码的重载(这不是一个专业化),你就可以完全开始了,因为现在没有相互冲突的推论。std::string

评论

0赞 duncan 9/27/2023
明白了。。。(注意:你对“不是专业化”的看法是完全正确的)
0赞 duncan 9/27/2023 #2

所以,多亏了@n-m-could-be-an-ai,我想出了这个解决方案

多谢

干杯

@Eugene评论后更新了解决方案

class Foo
{
  protected:
    int _x;
    std::string _name;

  public:
    void set_x(int x) { _x = x; }
    void set_name(const std::string & name) { _name = name; }

    int x() const { return _x; }
    std::string name() const { return _name; }
};

template <typename Container, typename T, typename Setter>
void container_set(Container & container, const T & value, Setter setter, std::type_identity_t<const T> * = nullptr)
{
    for (auto & foo : container)
    {
        (foo.*setter)(value);
    }
}


评论

0赞 Eugene 9/27/2023
您可以将其最后一个参数放在非推导上下文中,而不是复制函数。如果您使用的是 C++20,则有一个标准:en.cppreference.com/w/cpp/types/type_identity。如果没有,自己做是微不足道的.type_identity
0赞 duncan 9/27/2023
@Eugene。正确。多谢。虽然我不得不承认这个解决方案是由 Copilot 生成的,并且包含相当多的在我看来像黑魔法的东西......
1赞 ildjarn 9/27/2023
副驾驶给你的解决方案是典型的机器人垃圾——它可能会“工作”,但这不是 Eugene 的建议,也不会通过代码审查
0赞 Eugene 9/28/2023
如果删除无意义的 ,那么剩下的就是另一种解决方案 - 为您的成员函数类型提供单独的模板参数。, std::type_identity_t<const T> * = nullptr