Reputation: 1321
I would like to understand where I am going wrong in trying to minimize the verbosity of my member functions template specialization. I get compilation errors when doing so rather arbitrarily. Here's the version that works, which hopefully will shed some light on what I am trying to achieve:
#include <iostream>
#include <type_traits>
typedef int i32;
template<class T>
struct rtvec
{
private:
T* e;
i32 d;
public:
rtvec(i32 d) : d(d), e(new T[d]) {}
//template<typename Args...>
//rtvec()
rtvec(const rtvec& in) : d(in.d), e(new T[in.d])
{
for (i32 i = 0; i < d; ++i)
at(i) = in.at(i);
}
rtvec(rtvec<typename std::remove_pointer_t<T>>& in) : d(in.dim()), e(new T[in.dim()])
{
for (i32 i = 0; i < d; ++i)
e[i] = &in.at(i);
}
~rtvec() { delete[] e; }
i32 dim() const { return d; }
template<typename U=T,
typename std::enable_if_t<std::is_same_v<U,T>>* = nullptr,
typename std::enable_if_t<!std::is_pointer_v<U>>* = nullptr>
inline T& at(i32 i)
{
return e[i];
}
template<typename U = T,
typename std::enable_if_t<std::is_same_v<U, T>>* = nullptr,
typename std::enable_if_t<std::is_pointer_v<U>>* = nullptr>
inline typename std::remove_pointer_t<T>& at(i32 i)
{
return *e[i];
}
};
int main()
{
rtvec<float> v(2);
v.at(0) = 1;
v.at(1) = 2;
rtvec<float*> p = v;
p.at(0) = 5;
std::cout << v.at(0) << " " << v.at(1) << "\n";
return 0;
}
Basically I am trying to make a runtime variable dimensional vector class, which when instantiated with a pointer, can be used as a sort of reference to a vector of the same type (more precisely I have several arrays of each of the coordinates of a set of points and I want to use the "reference" vector to be able to work with those as if they were ordered the other way around in memory).
When I try to simplify the code, however, by trying to remove what I perceive as an unnecessary typename U
. I get the following compilation error in MSVC2017: std::enable_if_t<false,void>' : Failed to specialize alias template
. Here's the less verbose version that I was aiming at achieving:
struct rtvec
{
private:
T* e;
i32 d;
public:
rtvec(i32 d) : d(d), e(new T[d]) {}
template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
rtvec(const rtvec& in) : d(in.d), e(new T[in.d])
{
for (i32 i = 0; i < d; ++i)
at(i) = in.at(i);
}
template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
rtvec(rtvec<typename std::remove_pointer_t<T>>& in) : d(in.dim()), e(new T[in.dim()])
{
for (i32 i = 0; i < d; ++i)
e[i] = &in.at(i);
}
~rtvec() { delete[] e; }
i32 dim() const { return d; }
template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
inline T& at(i32 i)
{
return e[i];
}
template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
inline typename std::remove_pointer<T>::type& at(i32 i)
{
return *e[i];
}
};
If I modify this a little, however, it does compile:
template<class T>
struct rtvec
{
private:
T* e;
i32 d;
public:
rtvec(i32 d) : d(d), e(new T[d]) {}
template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
rtvec(const rtvec& in) : d(in.d), e(new T[in.d])
{
for (i32 i = 0; i < d; ++i)
at(i) = in.at(i);
}
/*template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
rtvec(rtvec<typename std::remove_pointer_t<T>>& in) : d(in.dim()), e(new T[in.dim()])
{
for (i32 i = 0; i < d; ++i)
e[i] = &in.at(i);
}*/
~rtvec() { delete[] e; }
i32 dim() const { return d; }
template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
inline T& at(i32 i)
{
return e[i];
}
/*template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
inline typename std::remove_pointer<T>::type& at(i32 i)
{
return *e[i];
}*/
};
(as long as the part relating to the pointer is commented out in main too). I want to understand what is it that makes the 2nd code not compile.
The reason I am not directly specializing the class is that in my original implementation I have a lot of other member functions that will be equivalent between the two specializations which I do not want to repeat.
Upvotes: 0
Views: 976
Reputation: 66200
When I try to simplify the code, however, by trying to remove what I perceive as an unnecessary
Unfortunately (if I understand correctly) you have removed something that is necessary
If I understand correctly, you have simplified the following methods
template<typename U=T,
typename std::enable_if_t<std::is_same_v<U,T>>* = nullptr,
typename std::enable_if_t<!std::is_pointer_v<U>>* = nullptr>
inline T& at(i32 i)
{
return e[i];
}
template<typename U = T,
typename std::enable_if_t<std::is_same_v<U, T>>* = nullptr,
typename std::enable_if_t<std::is_pointer_v<U>>* = nullptr>
inline typename std::remove_pointer_t<T>& at(i32 i)
{
return *e[i];
}
as follows
template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
inline T& at(i32 i)
{
return e[i];
}
template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
inline typename std::remove_pointer<T>::type& at(i32 i)
{
return *e[i];
}
Unfortunately SFINAE, over a template method, works only when uses tests (std::enable_if_t
) based on template parameters of the method itself.
I mean: SFINAE doesn't works when the std::enable_if_t
tests involve T
(and only T
) because T
is a template parameter of the struct, not a template parameter of the method.
So you need the trick
typename U = T
that "transform" the T
type in a template parameter of the method.
You can simplify a little removing the typename
before std::enable_if_t
because the "_t
" is exactly typename
(see the definition of std::enable_if_t
)
Off topic: I'm not a language layer but, as far I know,
std::enable_if_t<std::is_same_v<U,T>>* = nullptr
isn't perfectly legal; I suggest to rewrite using int
instead of void *
std::enable_if_t<std::is_same_v<U,T>, int> = 0
or maybe bool
std::enable_if_t<std::is_same_v<U,T>, bool> = true
or other integer types
Concluding, I suggest to rewrite your at()
method as follows
template <typename U = T,
std::enable_if_t<std::is_same_v<U, T>, int> = 0,
std::enable_if_t<!std::is_pointer_v<U>, int> = 0>
inline T& at(i32 i)
{
return e[i];
}
template<typename U = T,
std::enable_if_t<std::is_same_v<U, T>, int> = 0,
std::enable_if_t<std::is_pointer_v<U>, int> = 0>
inline typename std::remove_pointer_t<T>& at(i32 i)
{
return *e[i];
}
Upvotes: 5
Reputation: 4050
There are several reasons and you need to provide the full compilation error. Regarding your code, you seem to use C++17.
For instance, in this code you are trying to return a reference to the object stored in the array, right?:
inline typename std::remove_pointer<T>::type& at(i32 i) {
return *e[i];
}
Can be replaced with a more STL-Like code:
using reference = T&;
using const_reference = const T&;
reference at(i32 i) {
return e[i];
}
const_reference at(i32 i) const {
return e[i];
}
Or use auto
:
auto at(i32 i) const {
return e[i];
}
This is the way most of the STL containers work. For instance, if you access an std::vector<T*>
, it will return a reference to a T*, not a reference to the data where T points.
Regarding the SFINAE technique you are using, I am not sure if it's properly written.
For instance, take a look into this post to find information about the proper ways to write the conditions for selecting constructors. Small sumary:
template <typename = typename std::enable_if<... condition...>::type>
explicit MyAwesomeClass(MyAwesomeClass<otherN> const &);
For instance, if you want to enable a constructor only for those instance that do not hold a pointer type:
template<typename = typename std::enable_if_t<!std::is_pointer_v<T>>>
explicit rtvec(const rtvec& in) : d(in.d), e(new T[in.d]) {
for (i32 i = 0; i < d; ++i)
at(i) = in.at(i);
}
Now, regarding the fact that you are using C++17, you can us constexpr if
that will make your life much easy and handle the different situations. Something like this, I guess:
template <typename U>
explicit rtvec(const rtvec<U>& in) : d(in.d), e(new T[in.d]) {
for (i32 i = 0; i < d; ++i){
if constexpr (std::is_pointer<T>::value &&
std::is_pointer<U>::value) {
// ...
} else if constexpr (!std::is_pointer<T>::value &&
std::is_pointer<U>::value) {
// ...
} else {
// rest of possible combinations
}
}
}
Upvotes: 1