Can we use auto keyword instead of template?
Consider following example :
#include <iostream>
template <typename T>
T max(T x, T y) // function template for max(T, T)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max<int>(1, 2) << '\n'; // instantiates and calls function max<int>(int, int)
std::cout << max<int>(4, 3) << '\n'; // calls already instantiated function max<int>(int, int)
std::cout << max<double>(1, 2) << '\n'; // instantiates and calls function max<double>(double, double)
return 0;
}
So we can write it this way too :
#include <iostream>
auto max(auto x, auto y)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max(1, 2) << '\n';
std::cout << max(4, 3) << '\n';
std::cout << max(1, 2) << '\n';
return 0;
}
So, why should use auto keyword instead of template?
As #HolyBlackCat said in the comment, the snippets are not the same.
In the first snippet when you use templates, you confine the arguments of T max(T x, T y) to be of the same type. So if you take the template approach, the following code will not work:
int x = 3;
double y = 5.4;
max(3, 5.4);
However, if you take the second approach, you can compare two different data types (if permitted, of course). This is because both argument's auto will decide what it's going to get independently, so comparing a int and double in the second approach is totally valid.
Finally I found the answer to my question:
We can use abbreviated function templates if we're using the C++20 language standard. They are simpler to type and understand because they produce less syntactical clutter.
Note that our two snippets are not the same. The top one enforces that x and y are the same type, whereas the bottom one does not.
Related
I am trying to create a map with string as key and a generic method as value in C++, but I do not know if that is even possible. I would like to do something like that:
void foo(int x, int y)
{
//do something
}
void bar(std::string x, int y, int z)
{
//do something
}
void main()
{
std::map<std::string, "Any Method"> map;
map["foo"] = &foo; //store the methods in the map
map["bar"] = &bar;
map["foo"](1, 2); //call them with parameters I get at runtime
map["bar"]("Hello", 1, 2);
}
Is that possible? If yes, how can I realise this?
You can type-erase the function types into a container, then provide a template operator(). This will throw std::bad_any_cast if you get it wrong.
N.B. because of the type erasure, you will have to specify exactly matching arguments at the call site, as e.g. std::function<void(std::string)> is distinct from std::function<void(const char *)>, even though both can be called with a value like "Hello".
#include <any>
#include <functional>
#include <map>
#include <string>
#include <iostream>
template<typename Ret>
struct AnyCallable
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(std::forward<F>(fun))) {}
template<typename ... Args>
AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
Ret operator()(Args&& ... args)
{
return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...);
}
std::any m_any;
};
void foo(int x, int y)
{
std::cout << "foo" << x << y << std::endl;
}
void bar(std::string x, int y, int z)
{
std::cout << "bar" << x << y << z << std::endl;
}
using namespace std::literals;
int main()
{
std::map<std::string, AnyCallable<void>> map;
map["foo"] = &foo; //store the methods in the map
map["bar"] = &bar;
map["foo"](1, 2); //call them with parameters I get at runtime
map["bar"]("Hello, std::string literal"s, 1, 2);
try {
map["bar"]("Hello, const char *literal", 1, 2); // bad_any_cast
} catch (std::bad_any_cast&) {
std::cout << "mismatched argument types" << std::endl;
}
map["bar"].operator()<std::string, int, int>("Hello, const char *literal", 1, 2); // explicit template parameters
return 0;
}
The most (I cannot say best here) you can do is to use a signature erasure. That mean to convert the pointer to functions to a common signature type, and then convert them back to the correct signature before using them.
That can only be done in very special use cases (I cannot imagine a real world one) and will be highly unsecure: nothing prevent you to pass the wrong parameters to a function. In short: NEVER DO THIS IN REAL WORLD CODE.
That being said, here is a working example:
#include <iostream>
#include <string>
#include <map>
typedef void (*voidfunc)();
void foo(int x, int y)
{
std::cout << "foo " << x << " " << y << std::endl;
}
void bar(std::string x, int y, int z)
{
std::cout << "bar " << x << " " << y << " " << z << std::endl;
}
int main()
{
std::map<std::string, voidfunc> m;
m["foo"] = (voidfunc) &foo;
m["bar"] = (voidfunc)& bar;
((void(*)(int, int)) m["foo"])(1, 2);
((void(*)(std::string, int, int)) m["bar"])("baz", 1, 2);
return 0;
}
It gives as expected:
foo 1 2
bar baz 1 2
I could not find in standard whether this invokes or not Undefined Behaviour because little is said about function pointer conversions, but I am pretty sure that all common compilers accept that, because it only involve function pointers casting.
You cannot store functions with different signatures in a container like map, no matter if you store them as a function pointer or std ::function<WHATEVER>. The information about the signature of the function is one and only one in both cases.
The types for the value in map is one, meaning that the object stored in it are all of the same type.
So if your functions have all the same signature, then it's easy, otherwise, you have to abandon type safety and start walking in a very dangerous realm.
The one in which you erase the type information about the functions stored inside the map.
This translates to something like map<string, void*>.
It's best to illustrate this question with a simple program.
#include <iostream>
#include <utility>
void printPoint(std::pair <int, int> point) {
printPoint(point.first, point.second);
}
void printPoint(int x, int y) {
std::cout << x << ' ' << y << std::endl;
}
int main(void) {
std::pair <int, int> point(2, 3);
printPoint(point);
return 0;
}
I would like for the two printPoint functions to accomplish the same thing by taking advantage of overloads. However, while inside the scope of a particular overload of the printPoint function (in this case, the single-argument one), the compiler only lets me use that particular overload, so the code does not compile.
Why is this? Further, is there any way to mitigate this problem so that I don't have to re-write the same function body twice?
Declaration order matters here.
Move the 2nd overload (printPoint(int, int)) before the 1st one (printPoint(std::pair <int, int>); otherwise it's invisible inside the 1st overload. i.e.
void printPoint(int x, int y) {
std::cout << x << ' ' << y << std::endl;
}
void printPoint(std::pair <int, int> point) {
printPoint(point.first, point.second);
}
LIVE
I know that I shouldn't overload a function for just parameters differ only in one of them passed by copy and the other by reference:
void foo(int x)
{
cout << "in foo(int x) x: " << x << endl;
}
void foo(int& x)
{
cout << "in foo(int& x) x: " << x << endl;
}
int main()
{
int a = 1;
foo(5); // ok as long as there is one best match foo(int)
foo(a); // error: two best candidates so the call is ambiguous
//foo(std::move(a));
//foo(std::ref(an)); // why also this doesn't work?
}
So a code that uses std::bind can be like this:
std::ostream& printVec(std::ostream& out, const std::vector<int> v)
{
for (auto i : v)
out << i << ", ";
return out;
}
int main()
{
//auto func = std::bind(std::cout, std::placeholders::_1); // error: stream objects cannot be passed by value
auto func = std::bind(std::ref(std::cout), std::placeholders::_1); // ok.
}
So std::ref here to ensure passing by reference rather than by value to avoid ambiguity?
* The thing that matters me: Does std::bind() implemented some wrapper to overcome this issue?
Why I can't use std::ref in my example to help the compiler in function matching?
Now that you know passing by value and reference are ambiguous when overload resolution tries to compare them for choosing a best viable function, let's answer how would you use std::ref (or std::cref) to differentiate between pass-by-value and pass-by-reference.
It turns out to be ... pretty simple. Just write the overloads such that one accepts a int, and the other accepts a std::reference_wrapper<int>:
#include <functional>
#include <iostream>
void foo(int x) {
std::cout << "Passed by value.\n";
}
void foo(std::reference_wrapper<int> x) {
std::cout << "Passed by reference.\n";
int& ref_x = x;
ref_x = 42;
/* Do whatever you want with ref_x. */
}
int main() {
int x = 0;
foo(x);
foo(std::ref(x));
std::cout << x << "\n";
return 0;
}
Output:
Passed by value.
Passed by reference.
42
The function pass the argument by value by default. If you want to pass by reference, use std::ref explicitly.
Now let's answer your second question: how does std::bind deal with this type of scenario. Here is a simple demo I have created:
#include <functional>
#include <type_traits>
#include <iostream>
template <typename T>
struct Storage {
T data;
};
template <typename T>
struct unwrap_reference {
using type = T;
};
template <typename T>
struct unwrap_reference<std::reference_wrapper<T>> {
using type = std::add_lvalue_reference_t<T>;
};
template <typename T>
using transform_to_storage_type = Storage<typename unwrap_reference<std::decay_t<T>>::type>;
template <typename T>
auto make_storage(T&& obj) -> transform_to_storage_type<T> {
return transform_to_storage_type<T> { std::forward<T>(obj) };
}
int main() {
int a = 0, b = 0, c = 0;
auto storage_a = make_storage(a);
auto storage_b = make_storage(std::ref(b));
auto storage_c = make_storage(std::cref(c));
storage_a.data = 42;
storage_b.data = 42;
// storage_c.data = 42; // Compile error: Cannot modify const.
// 0 42 0
std::cout << a << " " << b << " " << c << "\n";
return 0;
}
It is not std::bind, but the method used is similar (it's also similar to std::make_tuple, which has the same semantic). make_storage by default copies the parameter, unless you explicitly use std::ref.
As you can see, std::ref is not magic. You need to do something extra for it to work, which in our case is to first decay the type (all references are removed in this process), and then check whether the final type is a reference_wrapper or not; if it is, unwrap it.
I am trying to create a map with string as key and a generic method as value in C++, but I do not know if that is even possible. I would like to do something like that:
void foo(int x, int y)
{
//do something
}
void bar(std::string x, int y, int z)
{
//do something
}
void main()
{
std::map<std::string, "Any Method"> map;
map["foo"] = &foo; //store the methods in the map
map["bar"] = &bar;
map["foo"](1, 2); //call them with parameters I get at runtime
map["bar"]("Hello", 1, 2);
}
Is that possible? If yes, how can I realise this?
You can type-erase the function types into a container, then provide a template operator(). This will throw std::bad_any_cast if you get it wrong.
N.B. because of the type erasure, you will have to specify exactly matching arguments at the call site, as e.g. std::function<void(std::string)> is distinct from std::function<void(const char *)>, even though both can be called with a value like "Hello".
#include <any>
#include <functional>
#include <map>
#include <string>
#include <iostream>
template<typename Ret>
struct AnyCallable
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(std::forward<F>(fun))) {}
template<typename ... Args>
AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
Ret operator()(Args&& ... args)
{
return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...);
}
std::any m_any;
};
void foo(int x, int y)
{
std::cout << "foo" << x << y << std::endl;
}
void bar(std::string x, int y, int z)
{
std::cout << "bar" << x << y << z << std::endl;
}
using namespace std::literals;
int main()
{
std::map<std::string, AnyCallable<void>> map;
map["foo"] = &foo; //store the methods in the map
map["bar"] = &bar;
map["foo"](1, 2); //call them with parameters I get at runtime
map["bar"]("Hello, std::string literal"s, 1, 2);
try {
map["bar"]("Hello, const char *literal", 1, 2); // bad_any_cast
} catch (std::bad_any_cast&) {
std::cout << "mismatched argument types" << std::endl;
}
map["bar"].operator()<std::string, int, int>("Hello, const char *literal", 1, 2); // explicit template parameters
return 0;
}
The most (I cannot say best here) you can do is to use a signature erasure. That mean to convert the pointer to functions to a common signature type, and then convert them back to the correct signature before using them.
That can only be done in very special use cases (I cannot imagine a real world one) and will be highly unsecure: nothing prevent you to pass the wrong parameters to a function. In short: NEVER DO THIS IN REAL WORLD CODE.
That being said, here is a working example:
#include <iostream>
#include <string>
#include <map>
typedef void (*voidfunc)();
void foo(int x, int y)
{
std::cout << "foo " << x << " " << y << std::endl;
}
void bar(std::string x, int y, int z)
{
std::cout << "bar " << x << " " << y << " " << z << std::endl;
}
int main()
{
std::map<std::string, voidfunc> m;
m["foo"] = (voidfunc) &foo;
m["bar"] = (voidfunc)& bar;
((void(*)(int, int)) m["foo"])(1, 2);
((void(*)(std::string, int, int)) m["bar"])("baz", 1, 2);
return 0;
}
It gives as expected:
foo 1 2
bar baz 1 2
I could not find in standard whether this invokes or not Undefined Behaviour because little is said about function pointer conversions, but I am pretty sure that all common compilers accept that, because it only involve function pointers casting.
You cannot store functions with different signatures in a container like map, no matter if you store them as a function pointer or std ::function<WHATEVER>. The information about the signature of the function is one and only one in both cases.
The types for the value in map is one, meaning that the object stored in it are all of the same type.
So if your functions have all the same signature, then it's easy, otherwise, you have to abandon type safety and start walking in a very dangerous realm.
The one in which you erase the type information about the functions stored inside the map.
This translates to something like map<string, void*>.
I can't figure out why the following code compiles fine:
#include <iostream>
void bar(int x) {
std::cout << "int " << x << std::endl;
}
void bar(double x) {
std::cout << "double " << x << std::endl;
}
template <typename A, typename B> // Note the order of A and B.
void foo(B x) {
bar((A)x);
}
int main() {
int x = 1;
double y = 2;
foo<int>(x); // Compiles OK.
foo<double>(y); // Compiles OK.
return 0;
}
But if I switch the order of A and B as below, then it won't compile:
#include <iostream>
void bar(int x) {
std::cout << "int " << x << std::endl;
}
void bar(double x) {
std::cout << "double " << x << std::endl;
}
template <typename B, typename A> // Order of A and B are switched.
void foo(B x) {
bar((A)x);
}
int main() {
int x = 1;
double y = 2;
foo<int>(x); // error: no matching function for call to ‘foo(int&)’
foo<double>(y); // error: no matching function for call to ‘foo(double&)’
return 0;
}
EDIT: Ad-hoc explanations are welcome, but would be better if someone can point out exact what the spec. says. Thanks!
In the first one, the compiler knows that A is int because you specifically tell it so with foo<int>, and it knows that B is also int because of the parameter that you pass it. So both A and B are known or can be deduced (you could say: A is supplied, B is implied).
However, in the second one, since B comes first and A doesn't appear in the parameter list, the compiler can't tell what A is and gives you an error. You're explicitly telling it what B is with foo<int>, and then the parameter you pass is also a B which, at the call, is an int which agrees with your previous explicit definition of B, but no mention is made of A, implicitly or explicitly, so the compiler must stop and error.
You don't really need the standard for this, it's just common sense. What on earth would A be in the second one?
Thanks for asking this question though, because I didn't realise you could explicitly specify some parameters and implicitly specify others in the parameter list before this.