代码之家  ›  专栏  ›  技术社区  ›  0xbadf00d

实现模板类的组件化和标量复用

  •  0
  • 0xbadf00d  · 技术社区  · 1 年前

    我有一个类似容器的模板类 foo 并希望实现“组件式”和“标量”乘法:

    #include <algorithm>
    #include <vector
    
    template<typename T>
    class foo
    {
    public:
        foo() = default;
        template<typename U>
        requires std::is_convertible_v<U, T>
        foo(foo<U> const& other) {
            std::copy(other.begin(), other.end(), begin());
        }
    
        template<typename U>
        requires std::is_convertible_v<U, T>
        foo& operator*=(foo<U> const& rhs)
        {
            std::transform(begin(), end(), rhs.begin(), begin(),
                [](T const& lhs, U const& rhs) { return lhs * rhs;});
            return *this;
        }
        template<typename U>
        requires std::is_convertible_v<U, T>
        foo& operator*=(U const& rhs)
        {
            std::transform(begin(), end(), begin(),
                [&rhs](T const& lhs) { return lhs * rhs; });
            return *this;
        }
        
        template<typename U>
        auto operator*(foo<U> const& rhs) const
        {
            foo<decltype(T{} * U{})> result = *this;
            return result *= rhs;
        }
        template<typename U>
        auto operator*(U const& rhs) const
        {
            foo<decltype(T{} * U{})> result = *this;
            return result *= rhs;
        }
    
        constexpr auto begin() noexcept { return m_x.begin(); }
        constexpr auto begin() const noexcept { return m_x.begin(); }
        constexpr auto end() noexcept { return m_x.end(); }
        constexpr auto end() const noexcept { return m_x.end(); }
    
    private:
        std::vector<T> m_x;
    };
    
    struct bar {
        operator foo<int>() const { return foo<int>{}; }
    };
    
    int main()
    {
        foo<int> a;
        bar b;
    
        auto const c = a * b;
    }
    

    现在,问题是:如果我们得到 foo<int> a bar b ,编译器如何知道 a * b 应该是分量乘法,而不是标量乘法?

    当我显式强制转换时,代码显然有效 b foo<int> ,但这真的有必要吗?

    Here is the code.

    0 回复  |  直到 1 年前
        1
  •  0
  •   Jan Schultke    1 年前

    您将函数模板与隐式转换混合在一起,这会带来麻烦。 一般来说,问题在于在模板参数推导过程中不考虑隐式转换,因此 bar 可转换为 foo<int> 不会使其选择所需的重载 foo<U> const& .

    添加非模板重载

    为了解决这个问题,您可以添加两个附加的非模板重载。 因为这些成员函数不是模板,所以隐式转换就像 bar -> foo<int> 按预期工作。

    这两个新的重载与现有的函数模板并不冲突,因为非模板在重载解决方面更匹配。

    auto operator*(foo const& rhs) const
    {
        auto result = *this;
        return result *= rhs;
    }
    
    auto operator*(T const& rhs) const
    {
        auto result = *this;
        return result *= rhs;
    }
    

    您可以在 constructor of std::pair . 有一个构造函数 std::pair 类型相同(复制构造函数),但也有一个构造函数模板接受 std::pair<U1, U2> 不同类型的。

    约束您的函数模板

    此外,无论这是否解决了您的问题,您都应该限制运算符重载。 你可以用 std::convertible_to concept 而不是依赖 std::is_convertible type trait :

    // Alternatively, specify the return type to be
    // foo<std::common_type_t<T, U>>; this also acts as a constraint.
    // Anything is better than completely unconstrained templates.
    template<std::convertible_to<T> U>
    auto operator*(foo<U> const& rhs) const
    {
        foo<decltype(T{} * U{})> result = *this;
        return result *= rhs;
    }
    
    template<std::convertible_to<T> U>
    auto operator*(U const& rhs) const
    {
        foo<decltype(T{} * U{})> result = *this;
        return result *= rhs;
    }
    

    看见 working solution at Compiler Explorer

    替代解决方案

    你也可以定义一个整体 operator* :

    template <typename U>
    // TODO: constrain
    auto operator*(U&& other) {
        // TODO: use if constexpr, traits, and other logic to decide what to do here
    }
    

    这种方法显然是正确的,但需要定义一个特性来检查类型是否 foo<X> 类似foo

    再次查看 std::pair constructor 。有一个构造函数模板正在接受 P&& ,即 成对的 .