Hi I was trying to implement a C++ concept-like feature (C++14) in C++11. The idea is just to write the wrapper function for std::for_each()
algorithm where I just check whether the 3rd argument is a function or not. So I wrote the following code, however I am not able to compile it as it should be. I am using Ubuntu12.04 with gcc4.8.1.
test_1.cpp
#include<type_traits>
#include<iostream>
#include<vector>
#include<algorithm>
void display(int& val) {
std::cout <<val<<std::endl;
}
template<typename T>
constexpr bool Is_valid_function(T& a) {
return std::is_function<T>::value;
}
template<typename T>
void check_valid_function(T& a) {
static_assert(Is_valid_function(a), "Not The Valid Function");
}
template <class InputIterator, class Function>
Function my_for_each(InputIterator first, InputIterator last, Function f) {
/* Concept Like Implementation to just check whether f is function or not */
check_valid_function(f);
return for_each(first, last, f) ;
}
void learn_static_assert_and_typetraits(void)
{
std::vector<int> vec_x{1,2,3,4,5};
my_for_each(vec_x.begin(), vec_x.end(), display);
}
int main(int argc, const char* argv[]) {
learn_static_assert_and_typetraits();
return 0;
}
I am getting the following compilation error from which I can see that the static_assert()
fails which is not correct as display
is valid function.
test_3.cpp: In instantiation of ‘void check_valid_function(T&) [with T = void (*)(int&)]’: test_3.cpp:27:26: required from ‘Function my_for_each(InputIterator, InputIterator, Function) [with InputIterator = __gnu_cxx::__normal_iterator >; Function = void (*)(int&)]’ test_3.cpp:35:50: required from here test_3.cpp:19:3: error: static assertion failed: Not The Valid Function static_assert(Is_valid_function(a), "Not The Valid Function"); ^
However if I do the same thing for the other type_traits function, I am getting the following error which is correct and expected.
test_2.cpp
#include<type_traits>
template<typename T>
constexpr bool Is_floating_point(T& a) {
return std::is_floating_point<T>::value;
}
template<typename T>
void f(T& a) {
static_assert(Is_floating_point(a), "Non-Float Type Data");
}
void learn_static_assert_and_typetraits(void) {
float y{10.0};
f(y);
int x{100};
f(x);
}
int main(int argc, const char* argv[]) {
learn_static_assert_and_typetraits();
return 0;
}
test_2.cpp: In instantiation of ‘void f(T&) [with T = int]’: test_2.cpp:19:6: required from here test_2.cpp:11:3: error: static assertion failed: Non-Float Type Data static_assert(Is_floating_point(a), "Non-Float Type Data"); ^
So, I wanted to understand why my first program is not working as it should be, whether there is bug in my code/understanding or it is something else. I hope the above data would be sufficient to understand my question. However if anyone wants some additional data, please let me know.
The issue is here:
template <class InputIterator, class Function>
Function my_for_each(InputIterator first, InputIterator last, Function f)
invoked via:
my_for_each(vec_x.begin(), vec_x.end(), display);
This deduces Function
(of my_for_each
) to be a function pointer; for
void display(int& val)
the deduced type is void(*)(int&)
. The type trait std::is_function
however checks if the passed type is a function type, not a function pointer type.
One solution is to remove the pointer:
template<typename T>
constexpr bool Is_valid_function(T& a) {
return std::is_function<typename std::remove_pointer<T>::type>::value;
}
But, as clang++ reveals, this still isn't sufficient:
template<typename T>
void check_valid_function(T& a) {
static_assert(Is_valid_function(a), "Not The Valid Function");
}
a
as a function parameter (even if check_valid_function
was constexpr
!) is not a compile-time constant, therefore it may not appear in a constant expression (inside the function body). Hence, Is_valid_function(a)
may not appear as the check for the static_assert
. It might be possible to use something similar to declval
, e.g.
static_assert(Is_valid_function( declval<T&>() ), "Not The Valid Function");
but unfortunately, declval
is not constexpr
, and I don't know how to write a constexpr
version. So, you could pass a pointer instead:
static_assert(Is_valid_function(static_cast<T*>(nullptr)),
"Not a valid function");
For this, you need to rewrite Is_valid_function
as follows:
template<typename T>
constexpr bool Is_valid_function(T*) {
return std::is_function<typename std::remove_pointer<T>::type>::value;
}
Note: the passed argument here is a pointer to a pointer to a function, but the parameter T*
deduced T
to be a pointer to a function, as before (hence the change in the signature). You might want to reflect that in the function name, if you choose this solution.
Other issues:
Relying on ADL for Standard Library algorithms
return for_each(first, last, f) ;
As far as I can see, this relies on ADL. But the iterators (and the function) are not required to be in namespace std
(even for vector::iterator
etc.), so you shouldn't rely on ADL:
return std::for_each(first, last, f);
Use of non-const refs for functions that don't need to modify their arguments, e.g.
constexpr bool Is_valid_function(T& a)
If you don't need to modify an argument, you should either pass it by value or by const reference, e.g.
constexpr bool Is_valid_function(T const& a)
"Wrong check" If this code is just for educational purposes, this isn't an issue. However, the check if the passed argument is of a function type is the "wrong check" when trying to check if the argument valid for a Standard Library algorithm. You should rather check whether f(*first)
is well-formed. This allows for function objects and checks if the argument type is "valid".