Assume we have a template class:
class Object<T>
And one instance of a variable:
Object<const IQuestion> qobj1(new Question());
Object<IQuestion> qobj2(new Question());
I would like to make a call to the function areEqual like this:
areEqual(question1, question2).
How to make a call to a function:
bool areEqual(const Object<IQuestion>& rhs, const Object<IQuestion>& rhs) const
Considering that variables are slightly different?
I assume that this somehow can be achieved with static_cast or reinterpret_cast.
The following probably does something similar to what you are looking for:
template<typename T, typename U>
std::enable_if_t<std::is_same<std::decay_t<T>, std::decay_t<U>>::value, bool>
areEqual(const Object<T>& lhs, const Object<U>& rhs) {
// T and U are the same type, put aside cv qualifiers and references
// if you know what's a meaningful way to compare them, do that
// note that here T and U can still have different cv-qualifiers
}
See a minimal, working example on coliru.
Related
I am working on an implementation of a shared pointer. (using C++17, in case it matters)
The only issue is the conversion constructor. I want to be able to static cast a smart_ptr to a smart_ptr of base type.
template<typename U>
inline smart_ptr(const smart_ptr<U>& rhs)
{
...
}
It works, but it will also try to cast a smart_ptr to a smart_ptr of any other type. For example, if I have an overloaded function that can take different kinds of smart_ptr's of unrelated type, I get a compiler error about ambiguous overload. So, I only want the conversion from smart_ptr -> smart_ptr if U is a derived class of T.
This looks like it should work. It compiles, but it does the opposite. It prevents the valid static upcasts from working, but still allows the casting to unrelated types:
template<typename U>
inline local_shared_ptr(typename enable_if<is_base_of<T,U>::value, const local_shared_ptr<U>&>::type rhs)
{
...
}
EDIT:
Got it working, thanks for the help. I choose jarod's solution, since I find template <typename U, enable_if_t<is_base_of<T,U>::value, int> = 0> the most concise. I didn't realize SFINAE could be that concise.
Also, since it was mentioned by Nathan:
Funnily enough, one of the issues I encountered is that I expected the template copy constructor to be called when the right-hand-side is the same type. Apparently, the compiler doesn't consider it an implementation of the copy constructor, and instead the auto-generated copy constructor was being called instead. Same issue for the move constructor and operator=. Not sure if that's a bug with MSVC2019.
U is non deducible with
template<typename U>
local_shared_ptr(enable_if_t<is_base_of<T,U>::value, const local_shared_ptr<U>&> rhs)
{
// ...
}
And as it is a constructor, you even cannot provide template explicitly.
So that constructor is useless.
You can use instead:
Default parameter (the most similar to your attempt IMO):
template <typename U>
local_shared_ptr(const local_shared_ptr<U>& rhs, enable_if_t<is_base_of<T,U>::value, int> = 0)
{
// ...
}
default template parameter (preferred way):
template <typename U, enable_if_t<is_base_of<T,U>::value, int> = 0>
local_shared_ptr(const local_shared_ptr<U>& rhs)
{
// ...
}
And as you use constructor, you cannot use return value.
Put the enable_if in the template parameter list like
template<typename U, std::enable_if_t<std::is_base_of_v<T, U>, bool> = true>
inline smart_ptr(const smart_ptr<U>& rhs)
{
}
And now this will only be called if U is T, or is derived from T. If you don't want to use this if U == T then you can use
template<typename U, std::enable_if_t<std::is_base_of_v<T, U> && !std::is_same_v<T, U>, bool> = true>
inline smart_ptr(const smart_ptr<U>& rhs)
{
}
I have a list of operator overloads, and I found that they are not acting in the way that I want them to. I reduced the problematic code to a couple lines to simulate the problem. I have a couple template type aliases to help compile time decision making, a template function (an operator in the working code, but here just a generic function), and a class called var that takes in two template parameters, an unsigned int and a bool (true = known var, false = unknown var, just mentioning this so that the naming convention in the following code snippet makes sense). Here is the code.
template<typename T>
using is_known = typename std::is_same<T, const var<T::ID, true> >::type;
template<typename T>
using is_unknown = typename std::conditional<is_known<T>::value, std::false_type, std::true_type>::type;
template<typename T>
using UK_if_UK = typename std::enable_if<is_unknown<T>::value, T>::type;
template<typename unknown_check_LHS, typename unknown_check_RHS>
constexpr const two_param_type<UK_if_UK<unknown_check_LHS>, UK_if_UK<unknown_check_RHS> > func(const unknown_check_LHS& lhs, const unknown_check_RHS& rhs)
{
return two_param_type<unknown_check_LHS, unknown_check_RHS>
(//generic construction goes here, unimportant...);
}
int main()
{
constexpr const var<0u,true> firstvar(123);
constexpr const var<1u,true> secondvar(456);
func(firstvar,secondvar);
func<std::decltype(firstvar),std::decltype(secondvar)>(firstvar,secondvar);
}
The two calls to func() seem identical to me, and they should both fail (in the working code there are other options once this function SFINAEs out). However, only the second call, where I explicitly declare the types, does the compiler throw an error. It compiles the first call to func() perfectly, and even worse, it operates and returns with type "known" (var<(some unsigned),true>), even though the type alias in the return type should SFINAE the function out of overload resolution. I am using operators, so explicitly declaring the type is not an option for me. More importantly, however, I want to know why the function is not SFINAEing out. Any help would be appreciated. Thank you.
Note: The specific error is:
error: no matching function for call to 'func(const var<0u, true>&, const var<1u, true>&)'
Also, I have tested is_known, is_unknown, and UK_if_UK to work properly, so no need to test those. Thank you.
The two calls to func seem identical to me
Wrong.
They are different.
It's a constness problem.
With
constexpr const var<0u,true> firstvar(123);
constexpr const var<1u,true> secondvar(456);
and the signature of func() as follows
template<typename unknown_check_LHS, typename unknown_check_RHS>
constexpr /* ... */ func (const unknown_check_LHS & lhs,
const unknown_check_RHS & rhs)
calling
func(firstvar,secondvar);
the types, unknown_check_LHS and unknown_check_RHS, are detected as var<0u, true> and var<1u, true> respectively.
Please observe: var<0u, true> and var<1u, true>, not const var<0u, true> and const var<1u, true>.
On the contrary, explicating the types as follows
func<decltype(firstvar), decltype(secondvar)>(firstvar,secondvar);
(and please: decltype(), not std::decltype()), unknown_check_LHS and unknown_check_RHS are explicated as const var<0u, true> and const var<1u, true> respectively.
Observe that now the types are constant.
Observe how is defined is_know
template<typename T>
using is_known = typename std::is_same<T, const var<T::ID, true> >::type;
It compare the type T with a constant type.
So, in the first case (with template types not-constant), is_known is false; in the second case (with constant template types), it's true.
I have found a very strange issue when revisiting and updating any old library. I have the following code
class bmint_tmp;
class bmfloat_tmp;
template<typename T> struct bop_return{typedef bmint_tmp type;};
template<> struct bop_return<float>{typedef bmfloat_temp type;};
class bmint
{
template<typename T> friend typename bop_return<T>::type operator+(const T& l, const bmint& r);
/** irrelevant code **/
};
template<typename T> typename bop_return<T>::type operator+(const T& l, const bmint& r)
{
return r.operator+(l);
}
template<> typename bop_return<bmint_tmp>::type operator+(const bmint_tmp& l, const bmint_tmp& r)=delete;
I have deleted this template instantiation because I prefer to get called the existing bmint_tmp::operator+, implemented as member method in another file. But, when compiling it seems that gcc dont see nothing but this deleted operator, and says:
error: use of deleted function 'typename::bigmath::bop_return::type bigmath::operator+(const T&, const bigmath::bmint&) [with T=...
I have attempted to change modifiers (remove const specification, for example), but my many attempts are unsuccessful. Can anyone help? Thanks in advance.
Yeah, if a function is deleted then it still exists (and it's even considered to be defined!), with everything that implies… you just can't actually call it.
Use enable_if, instead, so that the template specialisation cannot be instantiated at all. Then the only candidate will be the operator function you want.
The usual pattern for standard library function objects is to have a templated struct with a non-template operator(). For example, std::less looks something like so:
template <typename T>
struct less
{
bool operator()(const T& lhs, const T& rhs) const {
return lhs < rhs;
}
};
std::vector<float> vec = ...;
std::sort(vec.begin(), vec.end(), less<float>{});
My question is, why is this better than a non-template struct with a templated operator()? It seems that the above functor would be equivalent in operation to:
struct less2
{
template <typename T>
bool operator()(const T& lhs, const T& rhs) const {
return lhs < rhs;
}
};
std::vector<float> vec = ...;
std::sort(vec.begin(), vec.end(), less2{});
except that we get the bonus of automatic type deduction. Even better, if we wanted to, we could compare different types, provided it made sense to do so:
struct less
{
template <typename T, typename U>
bool operator()(const T& lhs, const U& rhs) const {
return lhs < rhs; // compile error if operator<(T, U) is not defined
}
};
and from there it's obvious how we could use decltype to get a truly generic std::plus, for example. But the standard library doesn't do it that way.
Of course, I'm sure that I'm not the first person that this has occurred to, and I'm sure that there's a very good reason that the standards committee decided to go with the first pattern and not the second. I'm just not sure what it is. Can any of the gurus enlighten me?
When the original functors were created none of the needed language facilities (return type deduction, perfect forwarding) did exist to solve the problem. The current design also has the benefit of allowing users to specialize the functors for their own types, even if this should strictly not be necessary.
C++1y introduces void specializations for all the functors (and uses void as a default argument) to make them easier to use. They will deduce and perfectly forward arguments and return types. This will also allow heterogeneous comparisons.
You will be able to write code like:
std::sort(begin(v), end(v), less<>());
The paper introducing this change is N3421.
Among the many benefits of const qualification is to make an API more understandable, example:
template<typename T> int function1(T const& in);
// clearly, the input won’t change through function1
With the introduction of rvalue references, one can benefit from perfect forwarding but often const qualifiers are removed, example:
template<typename T> int function2(T&& in);
// can explicitly forward the input if it's an rvalue
Apart from documentation, is there a good way to describe that function2 won’t change its input?
template<typename T> int function2(T&& in);
// can explicitly forward the input if it's an rvalue
Apart from documentation, is there a good way to describe that
function2 won’t change its input?
Yes. Stick with the C++03 solution:
template<typename T> int function1(T const& in);
// clearly, the input won’t change through function1
The benefits of perfect forwarding are that you don't want to assume if something is const or non-const, lvalue or rvalue. If you want to enforce that something is not modified (i.e. that it is const), then explicitly say so by adding const.
You could do this:
template<typename T> int function1(T const&& in);
// clearly, the input won’t change through function1
However everyone who read your code would wonder why you've used rvalue references. And function1 would cease to accept lvalues. Just use const & instead and everyone will understand. It is a simple and well understood idiom.
You don't want to perfectly forward. You want to enforce immutability.
You could say this:
template <typename T>
typename std::enable_if<immutable<T>::value, int>::type
function(T && in)
{
// ...
}
where you have something like:
template <typename T> struct immutable
: std::integral_constant<bool, !std::is_reference<T>::value> {};
template <typename U> struct immutable<U const &>
: std::true_type {};
This way, the template will only be usable if the universal reference is either a const-reference (so T = U const &) or an rvalue-reference (so T is not a reference).
That said, if the argument is not going to be changed, you could just use T const & and be done with it, since there's nothing to be gained from binding mutably to temporary values.