如何基于实例的模板类型过载函数

发布于 2025-02-13 21:03:49 字数 711 浏览 1 评论 0 原文

(我认为这不是模板成员模板类的功能专业化,而无需指定类模板参数 阅读有点困难,所以我可能错了。我还没有发现其他可能的相关问题)

是否有一种方法可以根据实例模板类型过载功能?

就我而言,我有一个数字类,可以实现模型操作员

template <typename X>
class Operand
{
private:
        X _val;
public:
        Operand *operator% (const Operand &other_number ) const
    {
       //here, i would like use fmod for floats and doubles, and % for integers
    }
];

,当然,总有一种解决方案,将所有内容施放成长双打,使用FMOD并将其铸造回X,但感觉

  • 非常效率非常低
  • ,非常尴尬
  • 并不是很谨慎,对C ++
  • 和C ++和可能以某种方式容易出现问题

(i don't think it's a duplicate of Template member function specialization of a templated class without specifying the class template parameter
reading it was a bit difficult, so i might be wrong. i also found no other possibly relevant questions)

is there a way to overload functions based on the instances template type?

in my case, i have a number class that implements a modulo operator

template <typename X>
class Operand
{
private:
        X _val;
public:
        Operand *operator% (const Operand &other_number ) const
    {
       //here, i would like use fmod for floats and doubles, and % for integers
    }
];

of course, there's always the solution of casting everything into long doubles, using fmod, and casting back into X, but it feels

  • terribly inefficient
  • Very awkward
  • not very idiomatic to C++
  • and probably pretty problem prone somehow

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

谜泪 2025-02-20 21:03:50

您在寻找这样的东西吗?

if constexpr (std::is_same_v<int, X>) {
    // use %
} else if constexpr (std::is_same_v<double, X> || std::is_same_v<float, X>) {
    // use fmod
} else {
    static_assert(!std::is_same_v<int, X>
               && !std::is_same_v<double, X>
               && !std::is_same_v<float, X>,
            "Unsupported type");
}

丑陋的解决方案,但也许这是您想要的。我的意思是,如果案件的数量不会改变,那么可能还可以。

但是,我会质疑总体设计并寻找诸如 CPO


如果您寻找一种“基于过载的”解决方案,那么您可以将所需的各种类型的 operator%专业化:

template <typename X>
class Operand
{
private:
        X _val;
public:
    Operand *operator% (const Operand &other_number ) const;
};

template<>
Operand<int> *Operand<int>::operator%(const Operand<int> &) const {
    // impl
}
template<>
Operand<double> *Operand<double>::operator%(const Operand<double> &) const {
    // impl
}
template<>
Operand<float> *Operand<float>::operator%(const Operand<float> &) const {
    // impl
}

不用说,这也是一个丑陋的解决方案。

Are you looking for something like this?

if constexpr (std::is_same_v<int, X>) {
    // use %
} else if constexpr (std::is_same_v<double, X> || std::is_same_v<float, X>) {
    // use fmod
} else {
    static_assert(!std::is_same_v<int, X>
               && !std::is_same_v<double, X>
               && !std::is_same_v<float, X>,
            "Unsupported type");
}

Ugly solution, but maybe it's what you're looking for. I mean, if the number of cases is not gonna change, then it's probably ok.

However, I would question the overall design and look for topics like CPO.


If you look for an "overload-based" solution, than you can specialize the operator% for the various types you need:

template <typename X>
class Operand
{
private:
        X _val;
public:
    Operand *operator% (const Operand &other_number ) const;
};

template<>
Operand<int> *Operand<int>::operator%(const Operand<int> &) const {
    // impl
}
template<>
Operand<double> *Operand<double>::operator%(const Operand<double> &) const {
    // impl
}
template<>
Operand<float> *Operand<float>::operator%(const Operand<float> &) const {
    // impl
}

Needless to say, this is a ugly solution as well.

我爱人 2025-02-20 21:03:50

当我提到cpos 在另一个答案中,我指的是Taht Taht tht the href =“ https://stackoverflow.com/questions/63818871/why-does-tag-ing-invoke-pattern-need-the-niebloid-s-niebloid-stdtag-invoke-at-al-all”>在这里。

但是,即使不遵循这些长度,我们也可以汲取一些灵感:

  • 实现操作数&lt; t&gt; :: operator%根据 helper function _VAL s 上作用于 operator%的参数,并传递给 helper
  • Overload/template-specialize helper _VAL )您要启用的类型>;
  • 操作员%中,调用 helper 应为 不合格的 ,它具有可以依靠ADL的优势只要匹配的助手过载和至少一个参数类型在同一命名空间中定义(不一定是相同;用于详细信息的ADL),就可以成功呼叫。
  • 要考虑到有时您不能在与要使用的参数相同的名称空间中定义 helper (例如,如果您打算使用 Operand&lt&std :: vector&lt ; int&gt;&gt; ,您无法在 namespace std {/ * shere */} 中定义 helper ,因为那是非法),令助手采用类型操作数的第一个additonal参数,该参数可作为标签,允许您在中定义 helper 操作数的名称空间。

这是一个粗略的尝试:

#include <math.h>
#include <iostream>

namespace operations { // namespace where Operand lives
template <typename X>
class Operand {
private:
public:
    struct Tag {};
    X _val;
    Operand() {};
    Operand(X v) : _val{v} {};
    Operand* operator% (Operand const& other_number ) const {
        std::cout << "dispatches to: ";
        return new Operand{helper(*this, this->_val, other_number._val)};
    }
};
}

using operations::Operand;

namespace operations {
// for builtin types, we need to resort to the namespace
// where Operand (the tag) is defined

// non-templated overload for int specifically
int helper(Operand<int> const&, int a, int b) {
    std::cout << "int" << std::endl;
    return a % b;
}

// same holds for those templates which we expect
// to instatiate for builtin types or types in std::

// templated overload for non-integrals
template<typename T, std::enable_if_t<!std::is_integral_v<T>, bool> = true>
auto helper(Operand<T> const&, T a, T b) {
    std::cout << "non-integrals" << std::endl;
    return std::fmod(a, b);
}

// templated overload for integrals
template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
auto helper(Operand<T> const&, T a, T b) {
    std::cout << "other integrals" << std::endl;
    return a % b;
}
}

namespace myspace {
struct MyType {
    MyType(int) {}
};

// can be implemented in the namespace where MyType lives

// overload for MyType
MyType helper(Operand<MyType> const&, MyType const&, MyType const&) {
    std::cout << "MyType" << std::endl;
    return 0;
}
}

int main() {
    Operand<int> i1{15};
    Operand<int> i2{6};
    Operand<unsigned int> u1{15};
    Operand<unsigned int> u2{6};
    Operand<double> d1{15};
    Operand<double> d2{6};
    Operand<float> f1{15};
    Operand<float> f2{6};
    Operand<myspace::MyType> m1{0};
    Operand<myspace::MyType> m2{0};

    (i1 % i2); // dispatches to: int
    (u1 % u2); // dispatches to: other integrals
    (d1 % d2); // dispatches to: non-integrals
    (f1 % f2); // dispatches to: non-integrals
    (m1 % m2); // dispatches to: MyType
}

但是我真的建议您通过我链接的那些读物以及从那里链接的那些读物。 CPO在通用编程中最重要。

When I mentioned CPOs in the other answer, I was referring to techniques taht are discussed a bit here.

However, even without going to those lengths, we can take just some inspiration:

  • implement Operand<T>::operator% in terms of a helper function acting on the _vals which are extracted from operator%'s arguments and passed to helper,
  • overload/template-and-specialize helper for the types (of _val) you want to enable;
  • in operator%, the call to helper should be unqualified, which has the advantage that you can rely on ADL for the call to be successful as long as the matching helper overload and at least one argument's type are in defined in the same namespace (well, not necessarily the same; study ADL for the details);
  • to take into account that sometimes you can't define helper in the same namespace as the argument you want to use it on (e.g., if you were planning to use Operand<std::vector<int>>, you can't define helper in namespace std { /* here */ } because that's illegal), let helper take a first additonal argument of type Operand, which acts as a tag and allows you to define helper in Operand's namespace.

Here's a rough attempt:

#include <math.h>
#include <iostream>

namespace operations { // namespace where Operand lives
template <typename X>
class Operand {
private:
public:
    struct Tag {};
    X _val;
    Operand() {};
    Operand(X v) : _val{v} {};
    Operand* operator% (Operand const& other_number ) const {
        std::cout << "dispatches to: ";
        return new Operand{helper(*this, this->_val, other_number._val)};
    }
};
}

using operations::Operand;

namespace operations {
// for builtin types, we need to resort to the namespace
// where Operand (the tag) is defined

// non-templated overload for int specifically
int helper(Operand<int> const&, int a, int b) {
    std::cout << "int" << std::endl;
    return a % b;
}

// same holds for those templates which we expect
// to instatiate for builtin types or types in std::

// templated overload for non-integrals
template<typename T, std::enable_if_t<!std::is_integral_v<T>, bool> = true>
auto helper(Operand<T> const&, T a, T b) {
    std::cout << "non-integrals" << std::endl;
    return std::fmod(a, b);
}

// templated overload for integrals
template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
auto helper(Operand<T> const&, T a, T b) {
    std::cout << "other integrals" << std::endl;
    return a % b;
}
}

namespace myspace {
struct MyType {
    MyType(int) {}
};

// can be implemented in the namespace where MyType lives

// overload for MyType
MyType helper(Operand<MyType> const&, MyType const&, MyType const&) {
    std::cout << "MyType" << std::endl;
    return 0;
}
}

int main() {
    Operand<int> i1{15};
    Operand<int> i2{6};
    Operand<unsigned int> u1{15};
    Operand<unsigned int> u2{6};
    Operand<double> d1{15};
    Operand<double> d2{6};
    Operand<float> f1{15};
    Operand<float> f2{6};
    Operand<myspace::MyType> m1{0};
    Operand<myspace::MyType> m2{0};

    (i1 % i2); // dispatches to: int
    (u1 % u2); // dispatches to: other integrals
    (d1 % d2); // dispatches to: non-integrals
    (f1 % f2); // dispatches to: non-integrals
    (m1 % m2); // dispatches to: MyType
}

But I really suggest to go through those readings I linked and those linked from there. CPOs are most important in generic programming.

红颜悴 2025-02-20 21:03:49

由于C ++ 17,因此使用如果ConstexPr 非常实用。但是,如果您有很多差异,则可能需要将浮点实现与积分类型实现分开。

以下所有实现都需要 #include&lt; type_traits&gt; ,也应遵循此免费功能:

template<class X>
Operand<X> operator%(const Operand<X>& l, const Operand<X>& r) {
    Operand rv{l};
    rv %= r;
    return rv;
}

一种方法是使用

template<class, bool> struct impl;

template<template<class> class O, class X>
struct impl<O<X>, true> { // specialization for floating point types
    using op_type = O<X>;
    // implement all special floating point behavior here:
    op_type& operator%=(const op_type& other) {
        op_type* This = static_cast<op_type*>(this);
        This->val = std::fmod(This->val, other.val);
        return *This;
    }
};

template<template<class> class O, class X>
struct impl<O<X>, false> { // specialization for integral types
    using op_type = O<X>;
    // implement all special functions for integral types here:
    op_type& operator%=(const op_type& other) {        
        auto This = static_cast<op_type*>(this);
        This->val %= other.val;
        return *This;
    }
};

template <class X, std::enable_if_t<std::is_arithmetic_v<X>, int> = 0>
class Operand : public impl<Operand<X>, std::is_floating_point_v<X>> {
private:
    using impl_type = impl<Operand<X>, std::is_floating_point_v<X>>;
    friend  impl_type;
    X val;
public:
    Operand() = default;
    Operand(X v) : val(v) {}
    // implement functions common to integral and floating point types here
};

​CRTP,看起来可能是这样的:

template<class, bool> struct impl;

template<class X>
struct impl<X, true> { // specialization for floating point types
    X remainder(X l, X r) const {
        return std::fmod(l, r);
    }
};

template<class X>
struct impl<X, false> { // specialization for integral types
    X remainder(X l, X r) const {
        return l % r;
    }
};

template <class X, std::enable_if_t<std::is_arithmetic_v<X>, int> = 0>
class Operand : private impl<X, std::is_floating_point_v<X>> {
private:
    X val;
public:
    Operand& operator%=(const Operand& other) {
        val = remainder(val, other.val);
        return *this;
    }
};

如果您想使用

template <class X, std::enable_if_t<std::is_arithmetic_v<X>, int> = 0>
class Operand {
private:
    X val{};
public:
    Operand() = default;
    Operand(X v) : val(v) {}

    template<class Y = X, std::enable_if_t<std::is_same_v<X, Y>, int> = 0>
    std::enable_if_t<std::is_floating_point_v<Y>, Operand&>
    operator%=(const Operand& other) {
        val = std::fmod(val, other.val);
        return *this;
    }
    template<class Y = X, std::enable_if_t<std::is_same_v<X, Y>, int> = 0>
    std::enable_if_t<std::is_integral_v<Y>, Operand&>
    operator%=(const Operand& other) {
        val %= other.val;
        return *this;
    }
};

由于C ++ 17,使用 constexpr,如果 通常比sfinae方法更喜欢:

template <class X, std::enable_if_t<std::is_arithmetic_v<X>, int> = 0>
class Operand {
private:
    X val{};
public:
    Operand() = default;
    Operand(X v) : val(v) {}

    Operand& operator%=(const Operand& other) {
        if constexpr (std::is_floating_point_v<X>) {
            val = std::fmod(val, other.val);
        } else {
            val %= other.val;
        }        
        return *this;
    }
};

Since C++17, using if constexpr is very practical. Though, if you have many differences you may want to separate the floating point implementation from the integral type implementation completely.

All the below implementations require #include <type_traits> and should also be followed by this free function:

template<class X>
Operand<X> operator%(const Operand<X>& l, const Operand<X>& r) {
    Operand rv{l};
    rv %= r;
    return rv;
}

One way of doing it would be to use the CRTP:

template<class, bool> struct impl;

template<template<class> class O, class X>
struct impl<O<X>, true> { // specialization for floating point types
    using op_type = O<X>;
    // implement all special floating point behavior here:
    op_type& operator%=(const op_type& other) {
        op_type* This = static_cast<op_type*>(this);
        This->val = std::fmod(This->val, other.val);
        return *This;
    }
};

template<template<class> class O, class X>
struct impl<O<X>, false> { // specialization for integral types
    using op_type = O<X>;
    // implement all special functions for integral types here:
    op_type& operator%=(const op_type& other) {        
        auto This = static_cast<op_type*>(this);
        This->val %= other.val;
        return *This;
    }
};

template <class X, std::enable_if_t<std::is_arithmetic_v<X>, int> = 0>
class Operand : public impl<Operand<X>, std::is_floating_point_v<X>> {
private:
    using impl_type = impl<Operand<X>, std::is_floating_point_v<X>>;
    friend  impl_type;
    X val;
public:
    Operand() = default;
    Operand(X v) : val(v) {}
    // implement functions common to integral and floating point types here
};

Without CRTP, it could look like this:

template<class, bool> struct impl;

template<class X>
struct impl<X, true> { // specialization for floating point types
    X remainder(X l, X r) const {
        return std::fmod(l, r);
    }
};

template<class X>
struct impl<X, false> { // specialization for integral types
    X remainder(X l, X r) const {
        return l % r;
    }
};

template <class X, std::enable_if_t<std::is_arithmetic_v<X>, int> = 0>
class Operand : private impl<X, std::is_floating_point_v<X>> {
private:
    X val;
public:
    Operand& operator%=(const Operand& other) {
        val = remainder(val, other.val);
        return *this;
    }
};

If you want to select the member function implementation using SFINAE:

template <class X, std::enable_if_t<std::is_arithmetic_v<X>, int> = 0>
class Operand {
private:
    X val{};
public:
    Operand() = default;
    Operand(X v) : val(v) {}

    template<class Y = X, std::enable_if_t<std::is_same_v<X, Y>, int> = 0>
    std::enable_if_t<std::is_floating_point_v<Y>, Operand&>
    operator%=(const Operand& other) {
        val = std::fmod(val, other.val);
        return *this;
    }
    template<class Y = X, std::enable_if_t<std::is_same_v<X, Y>, int> = 0>
    std::enable_if_t<std::is_integral_v<Y>, Operand&>
    operator%=(const Operand& other) {
        val %= other.val;
        return *this;
    }
};

Since C++17, using constexpr if is often preferred over the SFINAE method:

template <class X, std::enable_if_t<std::is_arithmetic_v<X>, int> = 0>
class Operand {
private:
    X val{};
public:
    Operand() = default;
    Operand(X v) : val(v) {}

    Operand& operator%=(const Operand& other) {
        if constexpr (std::is_floating_point_v<X>) {
            val = std::fmod(val, other.val);
        } else {
            val %= other.val;
        }        
        return *this;
    }
};
凹づ凸ル 2025-02-20 21:03:49

我认为,基于模板类型的函数超载的最干净的方法是使用C ++ 20约束。您可以执行以下操作:

template <typename X>
class Operand
{
    private:
        X val;
    public:
        Operand(X val): val{val} {}

        template<typename Y>
        requires std::same_as<Y, int>
        friend Operand operator% (const Operand<Y> lhs, const Operand<Y> &rhs ) {
            //here, Y is int
            return Operand<Y> { lhs.val % rhs.val };
        }

        template<typename Y>
        requires std::same_as<Y, float>
        friend Operand operator% (const Operand<Y> lhs, const Operand<Y> &rhs ) {
            //here, Y is float
            return Operand<Y> { std::fmod(lhs.val, rhs.val) };
        }
};

请注意,这也使用非成员的朋友定义,在我看来,这更像是对称性,更易于阅读。

如果您没有C ++ 20编译器,则可以使用 std :: Enable_if 进行完全相同的效果(但是,如果找不到匹配的操作员,则使用丑陋的编译器错误消息)。 std :: enable_if 可以以各种方式使用,我更喜欢以下一个:

template<typename Y, typename std::enable_if<std::is_same<Y, int>::value, bool>::type = true>
friend Operand operator% (const Operand<Y> lhs, const Operand<Y> &rhs ) {
    //here, Y is int
    //...
}

如果您发现自己为更多运算符/函数进行此操作,则应该完全分开针对不同类型的实现,如Ted Lyngmo的答案中所示。

I think the cleanest way to overload a function based on a template type is using the C++20 constraints. You can do something like the following:

template <typename X>
class Operand
{
    private:
        X val;
    public:
        Operand(X val): val{val} {}

        template<typename Y>
        requires std::same_as<Y, int>
        friend Operand operator% (const Operand<Y> lhs, const Operand<Y> &rhs ) {
            //here, Y is int
            return Operand<Y> { lhs.val % rhs.val };
        }

        template<typename Y>
        requires std::same_as<Y, float>
        friend Operand operator% (const Operand<Y> lhs, const Operand<Y> &rhs ) {
            //here, Y is float
            return Operand<Y> { std::fmod(lhs.val, rhs.val) };
        }
};

Note that this also uses non-member friend definitions, which is more symmetric and easier to read in my opinion.

If you don't have a C++20 compiler, you can use std::enable_if for exactly the same effect (but with uglier compiler error messages if no matching operator is found). std::enable_if can be used in various ways, from which I prefer the following one:

template<typename Y, typename std::enable_if<std::is_same<Y, int>::value, bool>::type = true>
friend Operand operator% (const Operand<Y> lhs, const Operand<Y> &rhs ) {
    //here, Y is int
    //...
}

If you find yourself doing this for many more operators/functions, you should propably completely separate the implementations for the different types, like e.g. shown in the answer of Ted Lyngmo.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文