I've got the following code for function traits:
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&)>
{};
and another Matrix class with the following signature:
#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;
};
This is being done as part of a C++ exercise in a course I'm a TA for. I'd like to try and find if my students passed their arguments as const references as we intended.
for example
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);
}
This seems to work well for operators that aren't overloaded... However I can't get the same tests to work for operators that are overloaded such as * or the constructors.
This seems to be because decltype can't distinguish overloaded methods, however I've tried a couple of things for the past couple of hours and can't get anything to work.
Any suggestions?
Edit* An example of something that won't work:
Thanks
C++ lets you specify more than one function of the same name in the same scope. These functions are called overloaded functions, or overloads. Overloaded functions enable you to supply different semantics for a function, depending on the types and number of its arguments.
When a function name is overloaded with different jobs it is called Function Overloading. In Function Overloading “Function” name should be the same and the arguments should be different. Function overloading can be considered as an example of a polymorphism feature in C++.
Function overloading is one of the important features of object-oriented programming. It allows users to have more than one function having the same name but different properties.
CPP. 2) Member function declarations with the same name and the name parameter-type-list cannot be overloaded if any of them is a static member function declaration.
A set of overloads is not a type. You cannot decltype
operator*
when it is overloaded, because you have to pick one overload first. This can be done via static_cast
as already has been suggested in a comment.
This answer explains an extremely useful idiom to check properties of types. For the sake of completeness I include the code here:
// 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 {};
It relies on SFINAE and it isnt something you couldn't write yourself. However, it refactors most of the boilerplate into the above generic part and all that is left is to define a template for the desired property. That template has to be "ok" for a type that has the desired property and it should fail for a type that doesn't have the desired property.
I decided to use a static_cast
and then decltype
on that. This alone looks a bit odd, but all it does is: Succeed when there is an operator*
of the desired signature and fail otherwise:
template <typename T>
using const_ref_derefop = decltype(static_cast< T&(T::*)(const T&) >(&T::operator*));
The same you can write for other operators or other signatures and the usage is:
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;
}
Output:
10
The crucial point is really just to cast the &T::operator*
to a member function pointer with the desired signature. This will fail if there is no overload with that signature.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With