I have a maths function that I want to be able to accept either a double, or a array/vector/container of doubles, and behave slightly differently.
I am attempting to use SFINAE and type traits to select the correct function.
Here is a minimal example:
#include <iostream>
#include <vector>
#include <type_traits>
template <typename T>
constexpr bool Iscontainer()
{
if constexpr (std::is_class<T>::value && std::is_arithmetic<typename T::value_type>::value) {
return true;
}
return false;
}
// Function 1 (double):
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type g(T const & t)
{
std::cout << "this is for a double" << t << std::endl;
}
// Function 2 (vec), version 1:
template <typename T>
typename std::enable_if<IsContainer<T>()>::type g(T const & t)
{
std::cout << "this is for a container" << t[0] << std::endl;
}
int main()
{
std::vector<double> v {1, 2};
std::array<double, 2> a {1, 2};
double d {0.1};
g<>(v);
g<>(a);
g<>(d); // error here
}
I get a compile time error:
../main.cpp:8:47: error: ‘double’ is not a class, struct, or union type
if constexpr (std::is_class<T>::value && std::is_arithmetic<typename T::value_type>::value) {
~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
However, when I replace function 2 with:
// Function 2 (vec), version 2:
template <typename T>
typename std::enable_if<std::is_class<T>::value && std::is_arithmetic<typename T::value_type>::value>::type
g(T const & t)
{
std::cout << "this is for a vector" << t[0] << std::endl;
}
It works.
My problem is I don't understand why the first version does not work..
And I prefer the readability of the first version.
The reason why it fails is simple. You do not invoke SFINAE, and when the compiler tries to evaluate the expressions it sees:
if constexpr (std::is_class<double>::value // this is fine it's false
&& std::is_arithmetic<typename double::value_type>::value // problem here!
)
The whole statement is evaluated, there is no short-circuit for the if. The closest solution to what you currently have is to explicitly split the if
, so that the problematic part is discarded when T
is not a class and the second check is nonsensical.
#include <iostream>
#include <vector>
#include <type_traits>
template <typename T>
constexpr bool IsVector()
{
if constexpr (std::is_class<T>::value) {
if constexpr (std::is_arithmetic<typename T::value_type>::value) {
return true;
}
}
return false;
}
// Function 1 (double):
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type g(T const & t)
{
std::cout << "this is for a double" << t << std::endl;
}
// Function 2 (vec), version 1:
template <typename T>
typename std::enable_if<IsVector<T>()>::type g(T const & t)
{
std::cout << "this is for a vector" << t[0] << std::endl;
}
int main()
{
std::vector<double> v {1, 2};
double d {0.1};
g<>(v);
g<>(d); // error here
}
Alternatively I'd suggest a using
alias:
template <typename T>
using IsVector2 = std::conjunction<typename std::is_class<T>, std::is_arithmetic<typename T::value_type> >;
template <typename T>
typename std::enable_if<IsVector2<T>::value>::type g(T const & t)
{
std::cout << "this is for a vector" << t[0] << std::endl;
}
You could also name it better. It doesn't really check whether T
is a vector
, or a container (after your edit). Your current definition is a bit loose as well.
It seems that simple overload does the job:
template <typename T>
void g(T const & t)
{
std::cout << "this is for a double" << t << std::endl;
}
template <typename T>
void g(const std::vector<T>& t)
{
std::cout << "this is for a vector" << t[0] << std::endl;
}
Demo
Your problem is that T::value_type
is evaluated for double
You have hard error (SFINAE doesn't occur there) before short circuit evaluation.
you might place this condition the constexpr part.
You might rewrite your function:
template <typename T>
constexpr bool IsVector()
{
if constexpr (std::is_class<T>::value) {
if constexpr (std::is_arithmetic<typename T::value_type>::value) {
return true;
}
}
return false;
}
Demo
Your second version use SFINAE
// Function 2 (vec), version 2:
template <typename T>
typename std::enable_if<std::is_class<T>::value
&& std::is_arithmetic<typename T::value_type>::value>::type
// SFINAE happens here for double ^^^^^^^^^^^^^
g(T const & t)