In this interview Stepanov shows how to implement generic max function in C++.
Try to implement a simple thing in the object oriented way, say, max.
I do not know how it can be done. Using generic programming I can
write:
template <class StrictWeakOrdered>
inline StrictWeakOrdered& max(StrictWeakOrdered& x,
StrictWeakOrdered& y) {
return x < y ? y : x;
}
and
template <class StrictWeakOrdered>
inline const StrictWeakOrdered& max(const StrictWeakOrdered& x,
const StrictWeakOrdered& y) {
return x < y ? y : x;
}
(you do need both & and const &).
Why is there need to write the code twice? Is this needed to aid compiler for optimization or a convention to reduce bugs? Is max a special case where body of a const version is identical?
How many valid const and non-const permutations a function of N arguments should have to define a complete API?
First of all, you need the non-const version to allow stuff like
max(a, b) = something;
If you don't want to do such things, you can just provide the const version only to cover all cases. That is basically what the standard std::max does.
You also do not need to provide any more permutations of const and non-const, returning non-const& only makes sense if all inputs are non-const, all other cases are properly handled by the const version.
If you want to avoid code duplication, you can do something like this:
template <class StrictWeakOrdered>
inline StrictWeakOrdered& max(StrictWeakOrdered& x, StrictWeakOrdered& y) {
const auto &xr = x;
const auto &yr = y;
return const_cast<StrictWeakOrdered&>(max(xr, yr));
}
In this special case, the const_cast is safe because you already know that the input is really non-const. Now you only have to provide the implementation for the const case.
So providing the implementation twice is not required and should not help the compiler, but whether or not the above is more readable than what Stepanov did is debatable.
You actually don't need both versions. You can write it this way.
template <class S, class T>
decltype(auto) max(S&& a, T&& b) {
using Ret = std::conditional_t<
std::is_const<std::remove_reference_t<S>>::value, S, T>;
if (b < a)
return std::forward<Ret>(a);
return std::forward<Ret>(b);
}
Falling back to const if either of the arguments was const.
If you do not intend to modify the argument, you can just go with the const& version. Everything should bind to a const reference.
C++11 also introduced reference collapsing, and a template parameter T&& is sometimes called a universal reference. In this case, when instantiating the parameter type for e.g. a int&, we would have int& && which collapses to int&. Now, you can write the function as
template <class T1, class T2>
inline T1 const& max(T1&& x, T2&& y) {
T1 const& x_=x;
T2 const& y_=y;
return (x_ < y_) ? (y_) : (x_);
}
This can be called with const values, temporaries (r-values) and mutable variables:
int const a=1;
int b=2;
max(b,b) = 23;
std::cout << max(a,a) << max( int{4}, int{5} ) << b << max(int{4}, a);
Related
I'm trying to pass function std::max as template parameter to a templated function, but for some reasons compiler prints error that function type cannot be deduced. A simple example reproduces the same issue. It works with own max2 function but doesn't work with STL std::max:
#include <algorithm>
template <class T>
T max2(const T& a, const T& b) { return std::max(a, b); }
int main() {
#if 1
auto f = max2<float>;
#else
// error: unable to deduce ‘auto’ from ‘max<float>’
auto f = std::max<float>;
#endif
float max_val = f(1.0f, 2.0f);
return 0;
}
As seen here, std::max<float> isn't a single, unambiguous function. At this point, it's an overload set and there are still two possibilities:
constexpr const float& max( const float& a, const float& b );
constexpr float max( std::initializer_list<float> ilist );
You have two main options:
Wrap it in a lambda:
auto f = [](float a, float b) { return std::max(a, b); };
// Note there's no by-reference behaviour in this lambda.
If you want something more reusable, you'll need to wrap it separately, e.g., as something that doesn't require shenanigans to pass around:
struct max_fn {
template<typename T>
const T& operator()(const T& a, const T& b) const {
return std::max(a, b);
}
};
Obviously #2 comes with significant boilerplate, and that's ignoring other overloads and constexpr. In the future, it is expected that you will be able to do better. Today, you could emulate such a thing with a macro (most simply done by making the macro expand into a lambda). I've come across at least one LIFT macro that does this.
There's a third option that can be appealing because it's one line (an ugly line, but one line), and that's casting to the correct function pointer type. However, this isn't allowed except in a few special cases per [namespace.std]/6.
In one system header file, I see the expression like this:
auto create_task(_Ty _Param) -> task<typename details::_TaskTypeFromParam<_Ty>::_Type>
{…}
I don't know what "->" means, it isn't pointer expression or lambda expression, can anyone help me?
It's the new function declaration syntax from C++11, and it's called the "trailing return type". At the end of a function declaration, -> means that the following is the return type of the function. It can only be used when the auto keyword is used instead of an actual return type where you would normally expect it.
For instance, these two declarations are compatible:
int foo();
auto foo() -> int;
Depending on your tastes, you may find it prettier than the old declaration syntax, especially when the return type is extremely long/complex:
task<typename details::_TaskTypeFromParam<_Ty>::_Type> create_task(_Ty _Param);
auto create_task(_Ty _Param) -> task<typename details::_TaskTypeFromParam<_Ty>::_Type>;
But sometimes it can be necessary with templates, when the return type of the function could vary with the arguments.
Say you want a templated function to add variables:
template<typename T>
T add(const T& x, const T& y)
{
return x + y;
}
That's great, but you'll only be able to add variables of the same type. Suppose you would like to be able to add variables of any type (like add((int)1, (double)2)).
template<typename T, typename U>
??? add(const T& x, const U& y)
{
return x + y;
}
EDIT: note that in C++14 and onwards, it's legal to write auto add(const T& x, const U& y), without a trailing return type, for function definitions (in other words, when you define the body of your function).
The problem is that you can't tell in advance what the result type of x + y will be. As templates stand, they could even be non-integral types. (Wouldn't you like to be able to do add(std::string("x"), "y")?)
Decltype, along with the new function declaration syntax, lets you solve this problem.
template<typename T, typename U>
auto add(const T& x, const U& y) -> decltype(x + y)
{
return x + y;
}
Decltype "returns" the type of an expression. Since you need x and y to have been declared for decltype(x + y) to work, you need the new syntax.
I'm writing a library which does numeric computations. I'm using templates so that the end-user can pick the precision they want. I would like this to work both with fundamental types (double, float) and high precision class types (e.g. boost::multiprecision). I'm wondering then if the argument types should be T or const & T.
On SO/google there are many posts about passing by value vs by reference. One of the "rules-of-thumb" seems to be:
Pass fundamental types by value
Pass everything else by const reference
However, this gets muddy if you have a template:
template<typename T>
T doSomething(T x, T y)
{
return x + y;
}
vs.
template<typename T>
T doSomething(const T & x, const T & y)
{
return x + y;
}
For boost::multiprecision you almost certainly want to pass by const reference. The question is whether passing double by const & is worse than by value. Many SO answers say const & is "no better and maybe worse"...but I couldn't find any good hard references.
I did the following benchmark
which seems to indicate there's no difference although maybe it depends on the simplicity of the function and inlining behavior.
There's the possibility of doing something like:
#include <type_traits>
template<typename T>
using choose_arg_type =
typename std::conditional<std::is_fundamental<T>::value,
T,
const T &>::type;
template <typename T>
T someFunc(choose_arg_type<T> arg)
{
return arg + arg;
}
int main()
{
auto result = someFunc<double>(0.0);
return 0;
}
But if it brings no benefit, it's added complexity and you lose type deduction (Any way to fix this type deduction?)
One reason I can think that pass by const reference is slower is, if it truly is using a reference, there may be cache locality issues. But if the compiler just optimizes to value...this won't matter.
What's the best way to handle this?
There is at least one circumstance where passing by const reference might disable optimizations. However, the most popular compilers provide a way to re-enable them.
Let’s look at this function:
int cryptographicHash( int& salt, const int& plaintext )
{
salt = 4; // Chosen by fair dice roll
// guaranteed to be random
return plaintext; // If we tell them there's a salt,
// this is the last hash function they'll
// ever suspect!
}
Looks pretty secure, right? But, since we’re writing in C++, is it as fast as it could possibly be? (Definitely what we want in a cryptographic hash.)
No, because what if you call it with:
int x = 0xFEED;
const int y = cryptographicHash( x, x );
Now the parameters passed by reference alias the same object, so the function should, as written, return 4, not 0xFEED. This means that, disastrously, the compiler can no longer optimize away the & in its const int& parameter.
However, the most popular compilers (including GCC, clang, Intel C++ and Visual C++ 2015 and up) all support the __restrict extension. So, change the function signature to int cryptographicHash( int& salt, const int& __restrict plaintext ) and all problems with it are solved forever.
Since this extension is not part of the C++ standard, you can improve portability with something like the following:
#if ( __GNUC__ || __clang__ || __INTEL_COMPILER || __ICL || _MSC_VER >= 1900 )
# define RESTRICT __restrict
#else
# define RESTRICT /**/
#endif
int cryptographicHash( int& salt, const int& RESTRICT plaintext );
(In GCC and clang, this does not appear to change the generated code.)
On platforms where the fundamental type in question fits into a register, a decent compiler should eliminate const references from parameters if it can see both sides of the call. For templates that is usually a given (unless they were explicitly instantiated somewhere). Since your library presumably has to be templated all the way down, this will apply to your case.
It's possible that your end users will have bad compilers or platforms where e.g. a double does not fit into a register. I don't see why you'd be incentivized to make micro-optimizations for these particular users, but maybe you do.
It's also possible that you want to explicitly instantiate all templates in your library for some set of types and provide implementation-less header files. In that case the user's compiler must obey whatever calling conventions exist on that platform and will probably pass fundamental types by reference.
Ultimately, the answer is "profile the relevant and representative use cases" if you don't have faith in the compiler(s).
Edit (removed macro solution): As suggested by Jarod42, the C++ way would be using an alias template. This also avoids the lack of deduction that the asker was running into with their original approach:
template<class T>
using CONSTREF = const T&; // Or just T for benchmarking.
https://godbolt.org/z/mopZ6B
As cppreference says:
Alias templates are never deduced by template argument deduction when deducing a template template parameter.
Passing something like int by reference (basically a pointer) is clearly sub-optimal since the extra indirection through the pointer can incur a cache miss and it may also prevent compiler optimizations since the compiler can't always know that the pointed-to variable cannot be changed by other entities, so it may in some cases be forced to do additional loads from memory. Passing by value removes the indirection and lets the compiler assume that noone else is changing the value.
This is a complex question that depends on architecture, compiler optimizations and many other specifics as the variation in answers shows. Since the OP is about writing template functions there is also the option of controlling which function is called by using SFINAE.
#include <iostream>
template <typename T, typename = typename std::enable_if_t<std::is_fundamental_v<T>> >
void f(T t) {
std::cout << "Pass by value\n";
}
template <typename T, typename = typename std::enable_if_t<not std::is_fundamental_v<T>> >
void f(T const &t) {
std::cout << "Pass by const ref.\n";
}
class myclass {};
int main() {
float x;
int i;
myclass c;
std::cout << "float: ";
f(x);
std::cout << "int: ";
f(i);
std::cout << "myclass: ";
f(c);
return 0;
}
Output:
float: Pass by value
int: Pass by value
myclass: Pass by const ref.
If an argument is trivially constructible and isn't modified, pass by value. The calling convention will automatically pass large structs by reference.
struct alignas(4096) page {unsigned char bytes[4096];};
[[nodiscard]] constexpr page operator^(page l, page r) noexcept {
for (int i = 0; i < 4096; ++i)
l.bytes[i] = l.bytes[i] ^ r.bytes[i];
return l;
}
Arguments that are modified and/or returned by nonconst reference must be passed by nonconst reference.
constexpr page& operator^=(page& l, page r) noexcept {return l = l ^ r;}
Pass any argument returned with const reference semantics by const reference.
using buffer = std::vector<unsigned char>;
[[nodiscard]] std::string_view to_string_view(const buffer& b) noexcept {
return {reinterpret_cast<const char*>(b.data()), b.size()};
}
Pass any argument deep copied to a different type by const reference.
[[nodiscard]] std::string to_string(const buffer& b) {
return std::string{to_string_view(b)};
}
Pass any non-trivially constructible, unmodified and non-deep copied argument by const reference.
std::ostream& operator<<(std::ostream& os, const buffer& b) {
os << std::hex;
for (const unsigned short u8 : b)
os << u8 << ',';
return os << std::dec;
}
Pass any argument deep copied to a value of the same type by value. There's no point in passing by reference an argument that's copied anyway, and the constructor for the returned copy is optimized away. See https://en.cppreference.com/w/cpp/language/copy_elision
[[nodiscard]] buffer operator^(buffer l, const buffer& r) {
const auto lsize = l.size();
const auto rsize = r.size();
const auto minsize = std::min(lsize, rsize);
for (buffer::size_type i = 0; i < minsize; ++i)
l[i] = l[i] ^ r[i];
if (lsize < rsize)
l.insert(l.end(), r.begin() + minsize, r.end());
return l;
}
This includes template functions as well.
template<typename T>
[[nodiscard]]
constexpr T clone(T t) noexcept(std::is_nothrow_constructible_v<T, T>) {
return t;
}
Otherwise take arguments of template parameter type by forwarding reference (&&). Note: && only has forwarding (universal) reference semantics in arguments of template parameter type, and/or for auto&& or decltype(auto)&&.
template<typename T>
constexpr bool nt = noexcept(std::is_nothrow_constructible_v<int, T&&>);
template<typename T>
[[nodiscard]]
constexpr int to_int(T&& t) noexcept(nt<T>) {return static_cast<int>(t);}
const auto to_int_lambda = [](auto&& t) noexcept(noexcept(to_int(t))) {
return to_int(t);
};
Suppose I have a template function foo() that takes two integer references as parameters. I'd like the template function to also automatically handle constant references (such as those from constants). Here is a generalized example. I can get foo() to work, but I have to provide a new implementation for every permutation of reference/const-reference parameters.
#include <iostream>
using namespace std;
template<typename A, typename B>
void foo(A& a, B& b)
{
cout<<"a:"<<a<<" b:"<<b<<endl;
}
template<typename A, typename B>
void foo(A& a, const B& b)
{
cout<<"a:"<<a<<" b:"<<b<<endl;
}
template<typename A, typename B>
void foo(const A& a, B& b)
{
cout<<"a:"<<a<<" b:"<<b<<endl;
}
template<typename A, typename B>
void foo(const A& a, const B& b)
{
cout<<"a:"<<a<<" b:"<<b<<endl;
}
int main()
{
int x = 0;
foo(x, x);
foo(x, 10);
foo(10, x);
foo(20, 20);
return 0;
}
The above example is a little bit contrived, but it is a generalization of what I am trying to do. In my more complex case, I have a class that acts as a wrapper to a set of parameters. The class constructor is templated, like foo(), and can have as many as 10 parameters. It would be a nightmare to enumerate all 2^10 possible constructors.
The problem that you describe is perfect forwarding problem. C++11 solved this problem with universal references:
template<typename A, typename B>
void foo(A&& a, B&& b) {
bar(std::forward<A>(a), std::forward<B>(b));
}
Parameters here are not rvalue references, but universal references. They will have the same ref-ness and const-ness as arguments.
If arguments are rvalues, in foo parameters will be rvalues with names. Named rvalues are lvalues. To pass parameters to sub-functions with preserved value-ness, you need to wrap them in std::forward. Function bar will get a and b with exactly the same type as foo.
If the template is not going to modify the arguments, then just offer the version with the const& and you should be fine:
template<typename A, typename B>
void foo(const A& a, const B& b)
{
cout<<"a:"<<a<<" b:"<<b<<endl;
}
If you pass a non-const lvalue, it will still be bound by a const reference and everything will work.
If you want some of the overloads to modify the arguments, then rethink the design, as those don't seem like functions that should share a name. There are exceptions, for examples, accessors into internal members of a structure, where you might want to return a const& if the object is const or a non-const reference otherwise... If that is the case, you can go the opposite way and offer only the non-const overload:
template<typename A, typename B>
void foo(A& a, B& b)
In this case, if the argument is a temporary or a non-const reference, the deduce type will reflect it and it will bind the argument with a const&.
int main() {
int a = 5;
const int b = 10;
foo(a,b); // foo<int,const int>(int&,const int&)
foo(10,b); // foo<const int,const int>(const int&, const int&)
}
Rereading your question it seems that you might be interested in perfect forwarding (this might or not fit your bill). If that is the case, and if you have a C++11 compiler you can use universal-references with a variadic template. Building a good wrapper is a hard thing, although you might be able to just use std::tuple as the actual storage, which should make the task quite simple.
Apart from all the answers posted which are correct, here is a simple program just for your reference as this might help you to understand the concept better.
#include<iostream>
template<class X>
void func(X& x, X& y)
{
//your code
}
template<class X, class Y>
void intermediateFunc(X&& x, Y&& y)
{
func(x,y);
}
int main()
{
int y = 9;
intermediateFunc(5,5);
intermediateFunc(y,5);
intermediateFunc(5,y);
intermediateFunc(y,y);
}
In one system header file, I see the expression like this:
auto create_task(_Ty _Param) -> task<typename details::_TaskTypeFromParam<_Ty>::_Type>
{…}
I don't know what "->" means, it isn't pointer expression or lambda expression, can anyone help me?
It's the new function declaration syntax from C++11, and it's called the "trailing return type". At the end of a function declaration, -> means that the following is the return type of the function. It can only be used when the auto keyword is used instead of an actual return type where you would normally expect it.
For instance, these two declarations are compatible:
int foo();
auto foo() -> int;
Depending on your tastes, you may find it prettier than the old declaration syntax, especially when the return type is extremely long/complex:
task<typename details::_TaskTypeFromParam<_Ty>::_Type> create_task(_Ty _Param);
auto create_task(_Ty _Param) -> task<typename details::_TaskTypeFromParam<_Ty>::_Type>;
But sometimes it can be necessary with templates, when the return type of the function could vary with the arguments.
Say you want a templated function to add variables:
template<typename T>
T add(const T& x, const T& y)
{
return x + y;
}
That's great, but you'll only be able to add variables of the same type. Suppose you would like to be able to add variables of any type (like add((int)1, (double)2)).
template<typename T, typename U>
??? add(const T& x, const U& y)
{
return x + y;
}
EDIT: note that in C++14 and onwards, it's legal to write auto add(const T& x, const U& y), without a trailing return type, for function definitions (in other words, when you define the body of your function).
The problem is that you can't tell in advance what the result type of x + y will be. As templates stand, they could even be non-integral types. (Wouldn't you like to be able to do add(std::string("x"), "y")?)
Decltype, along with the new function declaration syntax, lets you solve this problem.
template<typename T, typename U>
auto add(const T& x, const U& y) -> decltype(x + y)
{
return x + y;
}
Decltype "returns" the type of an expression. Since you need x and y to have been declared for decltype(x + y) to work, you need the new syntax.