I am trying to understand new c++ features and I came up with my implementation of max function:
#include <utility>
template <typename T>
concept bool Comparable() {
return requires(T a, T b)
{
{a > b} -> bool;
{a < b} -> bool;
{a >= b} -> bool;
{a <= b} -> bool;
};
}
template<Comparable T, Comparable U>
constexpr decltype(auto) max(T&& a, U&& b)
{
return std::forward<T>(a) > std::forward<U>(b) ? std::forward<T>(a) : std::forward<U>(b);
}
int main()
{
constexpr int a = 2, b = 3;
return max(a, b);
}
Can this replace the macro version?
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
And if not what can be improved?
I am compiling with gcc-8.1.0 and -std=c++2a, -fconcepts and -O3 flags
EDIT:
Thank you for your suggestions, this is how it looks now:
#include <utility>
template<typename T, typename U>
concept bool TypesLessComparable() {
return requires(T a, U b)
{
{a < b} -> bool;
};
}
template<typename T, typename U>
constexpr decltype(auto) max(T&& a, U&& b) requires (TypesLessComparable<T, U>() == true)
{
return a < b ? std::forward<U>(b) : std::forward<T>(a);
}
int main()
{
constexpr int a = 2, b = 3;
return max(a, b);
}
std::forward<T>(a) > std::forward<U>(b) ? std::forward<T>(a) : std::forward<U>(b);
You forward the result twice. This is wrong for most types (only OK for types that implement move as a deep copy). You should remove forward from the comparison.
Is this max function decent?
No, there are at least 3 issues:
1 Unclear why you request multiple comparison operations when your function only requires one of them (it should be fine for a class only to define operator> when only max() is used)
2 Concept Comparable requires that type has comparison operations on itself, you may compare 2 object of different types. This invalidates usage of concepts (a function should use only what required/provided by concepts)
3 You may return objects of different types as well, one may convert to another but you should cover that by another concept.
Here is code example for case 2 and 3:
struct A { friend bool operator>( const A&, const A& ); };
struct B { friend bool operator>( const B&, const B& ); };
auto x = max( A(), B() );
To summarize:
Your function should require minimum set of operations on type(s), necessary for it to operate.
If type(s) passed concepts validation your function should compile successfully.
This is most probably not full set of requirements.
Related
So I've recently learned about universal references and reference collapsing.
So let's say I have two different implementations of a max function like such.
template<class T>
T&& max(T&& a, T&& b)
{
return (a < b) ? b : a;
}
template<class T>
const T& max(const T& a, const T& b)
{
return (a < b) ? b : a;
}
One version takes its arguments by const reference and other takes them by universal reference.
What confuses me about these is when should either be used. Passing by const is mainly used so you can bind temporaries to it.
In the stl there's different uses as well. std::move takes universal reference where std::max takes by const ref.
What's the situations on where either should be used??
Say if I wanted to avoid the copy when it returns or keep a reference on return. Would it makes sense to have a const and nonconst version of the function etc
The first one should probably be:
// amended first version
template<class T>
decltype(auto) max(T&& a, T&& b) {
return (a < b) ? std::forward<T>(b) : std::forward<T>(a);
}
Otherwise it wouldn't work for temporaries (the original first option fails with two temporaries).
But even now, with the new version above, it cannot work for a mix of an rvalue and an lvalue:
int i = my_max(3, 5); // ok
i = my_max(i, 15); // fails
// no known conversion from 'int' to 'int &&' for 1st argument
To add to that, the return value of the first option is either an lvalue-ref or an rvalue-ref (with or without const, depending on the arguments). Getting back rvalue-ref to a temporary would not be the best idea, it would work if we immediately copy from it, but then it would be better to return byvalue, which is not the approach taken by the first version.
The pitfall with the second, the const-lvalue-ref version, is that if a temporary is sent to it, we return a const-lvalue-ref to a temporary which is bug prone (well, not more than returning an rvalue-ref to a temporary, but still). If you copy it immediately you are fine, if you take it by-ref you are in the UB zone:
// second version
template<class T>
const T& max(const T& a, const T& b) {
return (a < b) ? b : a;
}
std::string themax1 = max("hello"s, "world"s); // ok
const std::string& themax2 = max("hello"s, "world"s); // dangling ref
To solve the above problem, with the cost of redundant copying for the lvalue-ref case, we can have another option, returning byvalue:
// third version
template<class T1, class T2>
auto max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
The language itself took the 2nd option for std::max, i.e. getting and returning const-ref. And the user shall be careful enough not to take a reference to temporaries.
Another option might be to support both rvalue and lvalue in their own semantic, with two overloaded functions:
template<class T1, class T2>
auto my_max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
template<class T>
const T& my_max(const T& a, const T& b) {
return (a < b) ? b : a;
}
With this approach, you can always get the result as a const-ref: if you went to the first one you get back a value and extend its lifetime, if you went to the second one you bind a const-ref to a const-ref:
int i = my_max(3, 5); // first, copying
const int& i2 = my_max(i, 25); // first, life time is extended
const std::string& s = my_max("hi"s, "hello"s); // first, life time is extended
const std::string s2 = my_max("hi"s, "hello"s); // first, copying
const std::string& s3 = my_max(s, s2); // second, actual ref, no life time extension
The problem with the above suggestion is that it only takes you to the lvalue-ref version if both arguments are const lvalue-ref. If you want to cover all cases you will have to actually cover them all, as in the code below:
// handle rvalue-refs
template<class T1, class T2>
auto my_max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
// handle all lvalue-ref combinations
template<class T>
const T& my_max(const T& a, const T& b) {
return (a < b) ? b : a;
}
template<class T>
const T& my_max(T& a, const T& b) {
return (a < b) ? b : a;
}
template<class T>
const T& my_max(const T& a, T& b) {
return (a < b) ? b : a;
}
template<class T>
const T& my_max(T& a, T& b) {
return (a < b) ? b : a;
}
A last approach to achieve the overloading, and supporting all kind of lvalue-ref (const and non-const, including a mixture), without the need for implementing the 4 combinations for lvalue-ref, would be based on SFINAE (here presented with C++20 with a constraint, using requires):
template<class T1, class T2>
auto my_max(T1&& a, T2&& b) {
return (a < b) ? std::forward<T2>(b) : std::forward<T1>(a);
}
template<class T1, class T2>
requires std::is_lvalue_reference_v<T1> &&
std::is_lvalue_reference_v<T2> &&
std::is_same_v<std::remove_cvref_t<T1>, std::remove_cvref_t<T2>>
auto& my_max(T1&& a, T2&& b) {
return (a < b) ? b : a;
}
And if you bear with me for one past the last... (hope you are not in a bucket for monsieur state by now). We can achieve it all in one function!, and as a side benefit, even allow cases of comparison between derived and base if supported, so the following would work fine:
A a = 1;
B b = 2; // B is derived from A
// if comparison between A and B is supported, you can do
const A& max1 = my_max(a, b); // const-ref to b
const A& max2 = my_max(a, B{-1}); // const-ref to a temporary copied from a
const A& max3 = my_max(a, B{3}); // const-ref to B{3}
This would be the code to support this with a single function:
template<typename T1, typename T2>
struct common_return {
using type = std::common_reference_t<T1, T2>;
};
template<typename T1, typename T2>
requires std::is_lvalue_reference_v<T1> &&
std::is_lvalue_reference_v<T2> &&
has_common_base<T1, T2> // see code in link below
struct common_return<T1, T2> {
using type = const std::common_reference_t<T1, T2>&;
};
template<typename T1, typename T2>
using common_return_t = typename common_return<T1, T2>::type;
template<class T1, class T2>
common_return_t<T1, T2> my_max(T1&& a, T2&& b)
{
if(a < b) {
return std::forward<T2>(b);
}
return std::forward<T1>(a);
}
The machinery for the above can be found here.
For the variadic version of this, you can follow this SO post.
In a project I'm working on I have a templated function similar to this where all of the arguments should be of type T
#include <iostream>
template<typename T> bool aWithinBOfC(T a, T b, T c)
{
return std::abs(a - c) < b;
}
the issue I'm having is it won't compile if all of the arguments are not of the same type but it seems reasonable that it should implicitly cast similar types to the one with the highest resolution before evaluation. Is there any way to get a call like this to be valid?
int main()
{
double a{1.2};
double b{1.4};
float c{0.1f};
std::cout << aWithinBOfC(a, b, c) << std::endl;
}
Something along these lines, perhaps:
template<typename T>
bool aWithinBOfCImpl(T a, T b, T c) {
/* actual implemenattion */
}
template <typename ... Args>
auto aWithinBOfC(Args... args) {
return aWithinBOfCImpl<std::common_type_t<Args...>>(args...);
}
Demo
You don’t need implicit conversions at the call site. The compiler will implicitly convert the types to the largest one in the expression in the return statement.
template <class T, class U, class V>
bool aWithinBOfC(T a, U b, V c) {
return std::abs(a - c) < b;
}
Basically what I want is something simple, a function:
template<typename A, typename B>
is_the_same_value(A a, B b)
{
// return true if A is the same value as B
}
This seemingly simple question is is hard as using return a == b fails for int/unsigned mix,
e.g.
is_the_same_value(-1, 0xffffffff)
would return true. clang/gcc warn about this: comparison of integers of different signs
Using something like a == b && ((a > 0) == (b > 0)) works but still triggers the compiler warning.
You can use std::common_type to remove the warning.
#include <type_traits>
template<typename A, typename B>
bool is_numbers_equal(A a, B b) {
using C = typename std::common_type<A, B>::type;
return (a > 0) == (b > 0) && static_cast<C>(a) == static_cast<C>(b);
// or maybe `... && C(a) == C(b);`
}
Well, this looks fun to optimize it for the compiler for types that both are the "same":
#include <type_traits>
template<typename A, typename B>
typename std::enable_if<
std::is_integral<A>::value &&
std::is_integral<B>::value &&
(std::is_signed<A>::value ^ std::is_signed<B>::value)
, bool>::type
is_numbers_equal(A a, B b) {
using C = typename std::common_type<A, B>::type;
return (a >= 0) == (b >= 0) &&
static_cast<C>(a) == static_cast<C>(b);
}
template<typename A, typename B>
typename std::enable_if<
! (
std::is_integral<A>::value &&
std::is_integral<B>::value &&
(std::is_signed<A>::value ^ std::is_signed<B>::value)
)
, bool>::type
is_numbers_equal(A a, B b) {
return a == b;
}
For value based comparisons where no implicit conversions (signed/unsigned etc) are allowed, checking for type equality could be an option:
#include <type_traits>
template<typename A, typename B>
bool is_the_same_value(const A& a, const B& b)
{
if constexpr(!std::is_same_v<A,B>) return false;
else return a == b;
}
Can't you use std::is_integral to check whether both types are int? Then convert them to a signed long long and then compare them.
You can compare memories:
template<typename A, typename B>
bool is_the_same_value(A a, B b)
{
return std::memcmp(&a,&b,sizeof(A)) == 0;
// return true if A is the same value as B
}
This does not look like a hard problem to me. There are enough complications so I would not call the result "simple", but it's far from complex. Just deal with the identified issues one at a time, and make sure things are symmetric with respect to the two parameters.
Issue 1: converting a negative value to an unsigned type is bad.
Issue 2: comparing different types can cause compiler warnings.
template<typename A, typename B>
bool is_the_same_value(A a, B b)
{
// Address issue 1 by returning false if exactly one of the parameters
// is negative.
if ( a < 0 && b >= 0 )
return false;
if ( b < 0 && a >= 0 )
return false;
// At this point, either both a and b are negative (hence A and B are signed types)
// or both are non-negative (hence converting to an unsigned type is fine).
// Address issue 2 by explicitly casting. Make sure you cast both
// ways to keep things symmetric.
return a == static_cast<A>(b) &&
b == static_cast<B>(a);
}
Sure, there are clever ways to make this code more compact (and less readable), but isn't the compiler supposed to be able to do those optimizations for you?
A requires expression typically looks like: requires ( parameter-list(optional) ) { requirement-seq }.
Is it possible to form a disjunction(||) as a requirement in the sequence without using requires constraint-expr. For example something like:
template<typename T> concept FooBarExpert =
requires(T a, T b) {
{a.foo(b)} || {a.bar(b)}; // Req 1
{ a.baz() }; // Req 2
// and onward
};
Concepts are decomposed in conjunction and disjunction of atomic-constraints through a process called constraint normalization described in temp.constr.normal.
Only:
logical and &&,
logical or ||,
parenthesized expression ()
and id-expression of the form C<A1, A2, ..., An>, where C names a concept
are decomposed. All other expressions are atomic constraints.
So a require-expression is, as a whole, an atomic constraint. In the concept TS, require-expression were decomposed but in C++20 they are not. As far as I remember, I just read all the papers of the c++ committee related to concepts, the reason was that require-expression normalization may cause a complexity explosion that could penalize compilation speed.
So:
requires(T a, T b) {
requires requires(T a, T b){a.foo(b)}
|| requires(T a, T b){a.bar(b)}; // Req 1
{ a.baz() }; // Req 2
// and onward
};
is an atomic-constraint. And
requires(T a, T b) {
{a.foo(b)}
{ a.baz() }; // Req 2
// and onward
}
|| requires(T a, T b) {
{a.bar(b)}
{ a.baz() }; // Req 2
// and onward
};
is the disjunction of two atomic constraint. (the two requires-expression)
And finaly:
( requires(T a, T b) { a.foo(b); } || requires (T a, T b) { a.bar(b); } )
&& requires(T a, T b) { a.baz(); /* and onward */};
is the conjunction of a disjunction with an atomic constraint.
Define FooBarExpert as a conjunction (or disjunction) of the appropriate requires expressions.
template<typename T> concept bool FooBarExpert =
( requires(T a, T b) {
{ a.foo(b) };
} ||
requires(T a, T b) {
{ a.bar(b) };
}
) &&
requires(T a, T b) {
{ a.baz() };
};
Is it somehow possible to reflect the type of the argument of a function at compile time?
So that
int b = add(3, 6)
Would result in a template instantiation in the form of
int add(int a, int b) { return a + b }
out of some however declared function template
A add(A a, B b) { return a + b }
I don't know if that is possible with templates they do not really seem to be made for heavy meta-programming.
template<typename T>
T add(T a, T b) { return a + b; }
Then you can call this with any built-in type (int, short, double, float, etc), and it will instantiate the function add at compile time, according to the type you use.
Note that if you split this into header/source, you will have trouble:
add.h:
template<typename T>
T add(T a, T b);
add.cpp:
template<typename T>
T add(T a, T b) { return a + b; }
main.cpp:
#include "add.h"
int a = 3;
int b = 5;
int i = add(a, b);
When you try to compile this, it will fail at link time. Here's why.
Compiling add.obj does not instantiate the add template method -- because it's never called in add.cpp. Compiling main.obj instantiates the function declaration -- but not the function body. So at link time, it will fail to find the definition of the add method.
Simplest fix is to just put the entire template function in the header:
add.h:
template<typename T>
T add(T a, T b) { return a + b; }
and then you don't even need the add.cpp file at all.
Doesn't this do what you're asking?
template <typename A, typename B>
A add(A a, B b) { return a + b; }
This is hardly "heavy meta-programming".
This is exactly the sort of thing templates do.
template <typename T>
inline T add(T a, T b) { return a + b; }
Allowing two different types gets a little bit trickier but is also possible.
If you plan on using anything other than simple types you want (in a header file):
template <typename A>
inline A add(const A& a, const A& b) { return a + b; }
Note the 'inline'.
As noted by others, the issue with mixed types is how to determine the return type from the argument types. Suppose we stick to simple types and have:
template
inline A add(A a, B b) { return a + b; }
Then this fails (likely with only a warning):
double d = add(1, 1.5); // Sets d to 2.0
So you have to do some work. For example:
template<class A, class B>
struct Promote
{
};
template<class A>
struct Promote<A,A>
{
typedef A Type;
};
template<>
struct Promote<int, double>
{
typedef double Type;
};
template<>
struct Promote<double, int>
{
typedef double Type;
};
The add function becomes:
template<class A, class B>
inline typename Promote<A,B>::Type add(A a, B b)
{
return a + b;
}
What all this does for you is ensure that the return type is the one you specify for adding a given pair of types. This will work even for complex types.
Templates do all their magic at compile time, but that seems to be quite adequate for what you're asking:
template <class T>
T add(T a, T b) { return a + b; }
It does get a bit more complex when the two might not match, so (for example) you could add an int to a double and get a double result. The current standard doesn't really support that1; with C++0x you can look into auto and decltype for such tasks.
1Though Boost typeof will often do the job.