Reputation: 15
I am using template expression about a user-defined class and I don't know how to make the operator+ work. The following example is simplified from the original code that aims to use my own integer type with expression template, holding Int instances in the expression template tree.
Here is the minimal code that reproduces the problem:
#include <type_traits>
template <typename E>
class IntExpr
{
public:
//typedef typename E::Type Type; //KO
static constexpr bool is_leaf = false;
template <typename T>
T number() const
{
return static_cast<E const&>(*this).number();
}
template <typename T>
IntExpr& operator+=(T const& other)
{
*this = *this + other;
return *this;
}
};
template <typename E1, typename E2>
class IntAdd : public IntExpr<IntAdd<E1, E2> >
{
const E1 u_;
const E2 v_;
public:
static constexpr bool is_leaf = false;
IntAdd(E1 const& u, E2 const& v) : u_(u), v_(v)
{
}
};
template <typename T>
class Int : public IntExpr<Int<T> >
{
public:
typedef T Type;
static constexpr bool is_leaf = true;
template <typename E>
Int(IntExpr<E> const& expr)
{
number_ = expr.number();
}
explicit Int(int number) : number_{number}
{
}
T number() const
{
return number_;
}
Int& operator+=(Int const& other)
{
number_ += other.number_;
return *this;
}
private:
T number_;
};
template <typename E1, typename E2>
inline IntAdd<E1, E2> operator+(IntExpr<E1> const& u, IntExpr<E2> const& v)
{
return IntAdd<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}
template <typename T, typename S, typename std::enable_if_t<!std::is_base_of_v<S, T> >* = nullptr>
inline decltype(auto) operator+(IntExpr<T> const& lhs, S const& rhs)
{
//return lhs + Int<typename IntExpr<T>::Type>(rhs); //KO
return lhs + T(rhs);
}
template <typename T, typename S, typename std::enable_if_t<!std::is_base_of_v<S, T> >* = nullptr>
inline decltype(auto) operator+(S const& lhs, IntExpr<T> const& rhs)
{
//return rhs + Int<typename IntExpr<T>::Type>(lhs); //KO
return rhs + T(lhs);
}
int main()
{
auto const a((Int<int>(1) + 2) + 3);
auto const b((Int<long>(1) + 2) + 3);
return 0;
}
The complete error is (because int is not an Int instance) :
D:\programmation\cpp\TestTemplateExpression\main.cpp: In instantiation of 'decltype(auto) operator+(const IntExpr<E1>&, const S&) [with T = IntAdd<Int<int>, Int<int> >; S = int; std::enable_if_t<(! is_base_of_v<S, T>)>* <anonymous> = 0]':
D:\programmation\cpp\TestTemplateExpression\main.cpp:96:38: required from here
D:\programmation\cpp\TestTemplateExpression\main.cpp:84:18: error: no matching function for call to 'IntAdd<Int<int>, Int<int> >::IntAdd(const int&)'
84 | return lhs + T(rhs);
| ^~~~~~
D:\programmation\cpp\TestTemplateExpression\main.cpp:34:9: note: candidate: 'IntAdd<E1, E2>::IntAdd(const E1&, const E2&) [with E1 = Int<int>; E2 = Int<int>]'
34 | IntAdd(E1 const& u, E2 const& v) : u_(u), v_(v)
| ^~~~~~
D:\programmation\cpp\TestTemplateExpression\main.cpp:34:9: note: candidate expects 2 arguments, 1 provided
D:\programmation\cpp\TestTemplateExpression\main.cpp:26:7: note: candidate: 'constexpr IntAdd<Int<int>, Int<int> >::IntAdd(const IntAdd<Int<int>, Int<int> >&)'
26 | class IntAdd : public IntExpr<IntAdd<E1, E2> >
| ^~~~~~
D:\programmation\cpp\TestTemplateExpression\main.cpp:26:7: note: no known conversion for argument 1 from 'const int' to 'const IntAdd<Int<int>, Int<int> >&'
D:\programmation\cpp\TestTemplateExpression\main.cpp:26:7: note: candidate: 'constexpr IntAdd<Int<int>, Int<int> >::IntAdd(IntAdd<Int<int>, Int<int> >&&)'
D:\programmation\cpp\TestTemplateExpression\main.cpp:26:7: note: no known conversion for argument 1 from 'const int' to 'IntAdd<Int<int>, Int<int> >&&'
To convert int to an Int instance, I tried to use a typedef (commented lines) but the definition of the class is not complete at this moment.
:\programmation\cpp\TestTemplateExpression\main.cpp: In instantiation of 'class IntExpr<Int<int> >':
D:\programmation\cpp\TestTemplateExpression\main.cpp:41:7: required from 'class Int<int>'
D:\programmation\cpp\TestTemplateExpression\main.cpp:96:29: required from here
D:\programmation\cpp\TestTemplateExpression\main.cpp:7:34: error: invalid use of incomplete type 'class Int<int>'
7 | typedef typename E::Type Type; //KO
| ^~~~
D:\programmation\cpp\TestTemplateExpression\main.cpp:41:7: note: declaration of 'class Int<int>'
41 | class Int : public IntExpr<Int<T> >
| ^~~
Is there a trick to solve this problem? Maybe using std::conditional with recursion if IntExpr on leafs if is true but I don't know how.
In other words, if I understand well the situation:
template <typename E, typename T, typename std::enable_if_t<std::is_standard_layout_v<T> && std::is_trivial_v<T> >* = nullptr>
constexpr inline decltype(auto) operator+(IntExpr<E> const& u, T const& v)
{
//How to scan E to get S that IntType=Int<S>;
return IntAdd<E, IntType>(*static_cast<const E*>(&u), IntType(v));
}
A work around can be to fix S type (with char for example) as follow:
#include <iostream>
#include <type_traits>
template <typename E>
class IntExpr
{
public:
static constexpr bool is_leaf = false;
constexpr decltype(auto) number() const
{
return static_cast<E const&>(*this).number();
}
};
template <typename E1, typename E2>
class IntAdd : public IntExpr<IntAdd<E1, E2> >
{
public:
constexpr IntAdd(E1 const& u, E2 const& v) : u_(u), v_(v)
{
}
constexpr decltype(auto) number() const
{
return u_.number() + v_.number();
}
private:
E1 const u_;
E2 const v_;
};
template <typename T>
class Int : public IntExpr<Int<T> >
{
public:
static constexpr bool is_leaf = true;
template <typename E>
constexpr Int(IntExpr<E> const& expr)
{
number_ = expr.number();
}
template <typename S, std::enable_if_t<std::is_standard_layout_v<S> && std::is_trivial_v<S> >* = nullptr>
constexpr Int(S const& number) : number_(static_cast<T>(number))
{
}
constexpr T const& number() const
{
return number_;
}
private:
T number_{0};
};
template <typename E1, typename E2>
constexpr inline IntAdd<E1, E2> operator+(IntExpr<E1> const& u, IntExpr<E2> const& v)
{
return IntAdd<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}
template <typename E, typename T, typename std::enable_if_t<std::is_standard_layout_v<T> && std::is_trivial_v<T> >* = nullptr>
constexpr inline decltype(auto) operator+(IntExpr<E> const& u, T const& v)
{
return IntAdd<E, Int<char> >(*static_cast<const E *>(&u), Int<char>(v));
}
template <typename E, typename T, typename std::enable_if_t<std::is_standard_layout_v<T> && std::is_trivial_v<T> >* = nullptr>
constexpr inline decltype(auto) operator+(T const& u, IntExpr<E> const& v)
{
return IntAdd<Int<char>, E>(Int<char>(u), *static_cast<const E *>(&v));
}
int main()
{
auto const a(Int<long>(1));
auto const b(Int<long>(1) + 2);
auto const c((Int<long>(1) + 2) + 3);
std::cout << "a type " << typeid(a).name() << std::endl;
std::cout << "a number " << a.number() << std::endl; //expected 1
std::cout << "b type " << typeid(b).name() << std::endl;
std::cout << "b number " << b.number() << std::endl; //expected 3
std::cout << "c type " << typeid(c).name() << std::endl;
std::cout << "c number " << c.number() << std::endl; //expected 6
return 0;
}
Upvotes: -1
Views: 106
Reputation: 3571
Often, when such problems seem hard, a level of indirection helps.
Here I suggest introducing a type trait to identify Int
-like things and a function to obtain the value:
// Should be true for built-in integrals, Int, and IntAdd
template <typename T> struct IsInt;
// Should return the value of the argument, i.e.
// - the integral value if T is a built-in integral
// - t.number() otherwise
template <...> auto get_number(T t) { ... };
With such helpers, it becomes rather straight forward to build the appropriate solution without inheritance and casts.
#include <iostream>
#include <type_traits>
// --- Helpers
template <typename T> struct IsInt : public std::is_integral<T> {};
template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
auto get_number(T t) {
return t;
}
template <typename T, typename = std::enable_if_t<!std::is_integral<T>::value>>
auto get_number(const T &t) {
return t.number();
}
// Int class (needs to partially specialize IsInt)
template <typename T> class Int {
public:
explicit Int(int number) : number_{number} {}
T number() const { return number_; }
private:
T number_;
};
template <typename T>
struct IsInt<Int<T>> : public std::true_type {};
// IntAdd class (needs to partially specialize IsInt)
template <typename E1, typename E2> class IntAdd {
public:
IntAdd(E1 u, E2 v) : u_(u), v_(v) {}
auto number() const { return get_number(u_) + get_number(v_); }
private:
E1 u_;
E2 v_;
};
template <typename E1, typename E2>
struct IsInt<IntAdd<E1, E2>> : public std::true_type {};
// operator+ for everything that fulfills IsInt
template <typename E1, typename E2,
typename = std::enable_if_t<IsInt<E1>::value && IsInt<E2>::value>>
auto operator+(E1 u, E2 v) {
return IntAdd<E1, E2>(u, v);
}
// Sample usage
int main() {
auto const a(Int<long>(1));
auto const b(Int<long>(1) + 2);
auto const c((Int<long>(1) + 2) + 3);
std::cout << "a type " << typeid(a).name() << std::endl;
std::cout << "a number " << a.number() << std::endl; //expected 1
std::cout << "b type " << typeid(b).name() << std::endl;
std::cout << "b number " << b.number() << std::endl; //expected 3
std::cout << "c type " << typeid(c).name() << std::endl;
std::cout << "c number " << c.number() << std::endl; //expected 6
return 0;
}
Note: The question does not clearly state what you would like to achieve in detail, but this should hopefully give you what you need to get there.
Upvotes: 0
Reputation: 1652
The outermost error states:
D:\programmation\cpp\TestTemplateExpression\main.cpp: In instantiation of 'decltype(auto) operator+(const IntExpr<E1>&, const S&) [with T = IntAdd<Int<int>, Int<int> >; S = int; std::enable_if_t<(! is_base_of_v<S, T>)>* <anonymous> = 0]':
D:\programmation\cpp\TestTemplateExpression\main.cpp:96:38: required from here
'here' being
auto const a((Int<int>(1) + 2) + 3);
^~~~~~
So the (Int<int>(1) + 2)
expression type is IntExpr<IntAdd<Int<int>, Int<int> >>
, which I believe is expected.
Then outer operator+
imlementation tries to cast const int&
to IntAdd<Int<int>, Int<int> >
which is just wrong: int
is not a sum expression. It fails, which is good: type system prevented us from doing something insane.
The question becomes, why do you both try to reflect the expression tree in the expression type and demand both summands to have the same type? Those requirements are not consistent with complex expressions, you should drop one of them.
Dropping the latter should be easy: you either remove explicit
from Int<T>
constructor and remove operator+ overloads completely or change casts to Int<S>(rhs)
.
Dropping the former requires better understanding what did you need this whole type contraption for, so I can't provide any meaningful recommendation based on the question as written..
Upvotes: 2