重载成员函数的函数特征

function traits for overloaded member functions

我有以下函数特征代码:

    template<typename T>
struct function_traits;

template<class F>
struct function_traits;

// function pointer
template<class R, class... Args>
struct function_traits<R(*)(Args...)> : public function_traits<R(Args...)>
{};

template<class R, class... Args>
struct function_traits<R(Args...)>
{
    using return_type = R;

    static constexpr std::size_t arity = sizeof...(Args);

    template <std::size_t N>
    struct argument
    {
        static_assert(N < arity, "error: invalid parameter index.");
        using type = typename std::tuple_element<N,std::tuple<Args...>>::type;
    };
};

// member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...)> : public function_traits<R(C&,Args...)>
{};

// const member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...) const> : public function_traits<R(C&,Args...)>
{};

// member object pointer
template<class C, class R>
struct function_traits<R(C::*)> : public function_traits<R(C&)>
{};

和另一个具有以下签名的矩阵 class:

#include <iostream>

/**
 * @struct MatrixDims
 * @brief Matrix dimensions container
 */
typedef struct MatrixDims
{
    int rows, cols;
} MatrixDims;

/**
 * Matrix class (only for 2D matrices)
 */
class Matrix
{
public:
    const std::string errInvalidDims      = "Error: Invalid matrices dimensions for this operation.";
    const std::string errIndexOutOfRange  = "Error: Index out of range.";
    const std::string errFailToLoadMatrix = "Error: Failed to load elements to matrix from file.";

    /**
     * Matrix object constructor
     * @param n Amount of rows
     * @param m Amount of columns
     */
    Matrix(int n, int m);

    /***
     * Copy Constructor for Matrix
     * @param m Matrix to be copied
     */
    Matrix(const Matrix &m);

    /**
     * default C'tor
     */
    Matrix(): Matrix(1,1) {};

    /***
     * Destructor for Matrix
     */
    ~Matrix();

    //########################## Operator Overloading Functions ########################################

    /**
     * Operator overload for = operator
     * @param m Matrix to be copied
     * @return reference to calling object
     */
    Matrix& operator=(const Matrix &M);


    /**
     * Operator overload for Matrix multiplication.
     * @param M: right side Matrix
     * @return  new matrix with new dimensions
     * (Num of rows = number of rows of calling matrix, Num of Columns = num of columns as right side matrix)
     */
    Matrix operator*(const Matrix &M) const;

    /**
     * Operator Overload for Matrix addition, on invalid
     * @param M Right side Matrix
     * @return new matrix with same dimension as calling matrix
     */
    Matrix operator+(const Matrix &M) const;

    /**
     * Operator Overload for Matrix +=
     * @param M Right side Matrix
     * @return reference to the calling matrix after the addition
     */
    Matrix& operator+=(const Matrix &M);

    /**
     * Overload to () operator, allows access and change of the i,j coordinate
     * @param i - ith row
     * @param j - jth column
     * @return reference to the value stored in the i,j coordinate
     */
    float& operator()(int i, int j);

    /**
     * Overload to () operator (const version), allows access to i,j coordinate
     * @param i - ith row
     * @param j - jth column
     * @return reference to the value stored in the i,j coordinate
     */
    const float& operator()(int i, int j) const;

    /**
     * Overload to the [] operator, allows access and modification of the ith Matrix value as if
     * it were flattened in to a vector.
     * For example: Matrix M of shape (2,3) M[5] = M(1,2) (with column and row numbers starting from 0)
     * @param i-ith coordinate of the flattened vector
     * @return reference to the value in the ith coordinate
     */
    float& operator[](int i);

    /**
     * Overload to the [] operator,(const version) allows access of the ith Matrix value as if
     * it were flattened in to a vector.
     * For example: Matrix M of shape (2,3) M[5] = M(1,2) (with column and row numbers starting from 0)
     * @param i-ith coordinate of the flattened vector
     * @return reference to the value in the ith coordinate
     */
    const float& operator[](int i) const;


    /**
     * Image Prints the calling Matrix object
     * @param os
     * @return reference to the os
     */
    friend std::ostream& operator<<(std::ostream &os, const Matrix& M);

    /**
     * Input of binary data to matrix
     * @param is input stream to read from
     * @param M matrix to fill
     * @return ref to is
     */
    friend std::istream& operator>>(std::istream &is, Matrix& M);
    /**
     * Scalar multiplication on the right of the matrix
     * @param left
     * @param right
     * @return
     */
    Matrix operator*(const float& right);
    /**
     * Scalar multiplication on the left of the matrix
     * @param left
     * @param right
     * @return
     */
    friend Matrix operator*(const float& left, const Matrix& right);


    /***
     * getter for the number of rows
     */
    int getRows() const {return rows;}

    /***
     * getter for the number of columns
     */
    int getCols() const {return cols;}

    /**
     * Transforms a matrix to a column vector.
     * @return ref to this
     */
    Matrix& vectorize() {
        rows = rows * cols;
        cols = 1;
        return *this;
    }

    /**
     * Plain prints this matrix, simply prints the elemnts space separated.
     */
    void plainPrint(){
        for(int i = 0; i < getRows(); i++){
            for(int j = 0; j < getCols(); j++){
                std::cout << (*this)(i,j) << " ";
            }
            std::cout << std::endl;
        }
    }

    /**
     * Plain prints this matrix, simply prints the elemnts space separated. const version
     */
    void plainPrint() const{
        for(int i = 0; i < getRows(); i++){
            for(int j = 0; j < getCols(); j++){
                std::cout << (*this)(i,j) << " ";
            }
            std::cout << std::endl;
        }
    }



private:
    float *matrix;
    int rows, cols;

};

这是在我担任助教的课程中作为 C++ 练习的一部分完成的。 我想尝试看看我的学生是否按照我们的预期将他们的参数作为 const 引用传递。

例如

using Traits = function_traits<decltype(&Matrix::operator=)>;
    if(!std::is_same<const Matrix&, Traits::argument<1>::type>::value)
    {
        std::cerr << "Operator= does not accept by const reference" << std::endl;
        exit(2);
    }

这似乎适用于未重载的运算符...但是我无法对重载的运算符(例如 * 或构造函数)进行相同的测试。

这似乎是因为 decltype 无法区分重载方法,但是在过去的几个小时里我尝试了一些方法,但没有任何效果。

有什么建议吗?

编辑* 一个不起作用的例子:

谢谢

一组重载不是一种类型。重载时不能 decltype operator* ,因为你必须先选择一个重载。这可以通过 static_cast 完成,正如评论中已经建议的那样。

这个 answer 解释了一个非常有用的习惯用法来检查类型的属性。为了完整起见,我在此处包含代码:

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

它依赖于 SFINAE,它不是你不能自己写的东西。但是,它将大部分样板重构为上述通用部分,剩下的就是为所需 属性 定义模板。对于具有所需 属性 的类型,该模板必须“正常”,对于不具有所需 属性.

的类型,它应该失败

我决定使用 static_cast,然后再使用 decltype。这看起来有点奇怪,但它所做的只是:当存在所需签名的 operator* 时成功,否则失败:

template <typename T>
using const_ref_derefop = decltype(static_cast<  T&(T::*)(const T&) >(&T::operator*));

你可以为其他运营商或其他签名编写相同的用法是:

struct A {
    A& operator*(const A&);
    A& operator*(A);
};
struct B {
    B& operator*(B);
};

int main() {
    std::cout << detect<A,const_ref_derefop>::value;
    std::cout << detect<B,const_ref_derefop>::value;
}

输出:

10

关键点实际上只是将 &T::operator* 转换为具有所需签名的成员函数指针。如果该签名没有重载,这将失败。