I know that it is possible to get a random tuple element at runtime. Behind the scenes, make_integer_sequence and integer_sequence can get all elements at compile time and offer access to these elements at runtime.
But my problem is that it seems only to be possible to access the elements of a random index only with a lambda or function pointer. I would like to get the lambda to return the element-reference so that I could get something like this:
auto myElement = runtime_get(mytuple, 5);
I don't know how I could do this. Working with templates isn't that new to me but templates can get very difficult to understand if they become complex. I'm trying to learn more about them by playing a little bit with the tuple-stuff.
I think, this should work: runtime_get<std::string>(0, mytuple), so the type is predetermined.
Yes, this is certainly possible, but you need to do something if the runtime index isn't the right type. For example, throw an exception.
Here's one sample implementation, but note that I condensed it at the expense of some readability. Member function pointers and templated lambdas is possibly the worst combination of features the language has to offer, but it was pretty concise (live example):
template<typename Result, typename... Ts>
auto runtime_get(std::size_t i, std::tuple<Ts...>& t) -> Result& {
using Tuple = std::tuple<Ts...>;
// A set of functions to get the element at one specific index
auto get_at_index = []<std::size_t I>(Tuple& tuple) -> Result& {
if constexpr (std::is_same_v<std::tuple_element_t<I, Tuple>, Result>) {
return std::get<I>(tuple);
} else {
throw std::runtime_error("Index does not contain the right type");
}
};
// Regular index_sequence trick to get a pack of indices
return [&]<std::size_t... Is>(std::index_sequence<Is...>) -> Result& {
// The type of a single member function pointer of the closure type, using awkward memfun syntax
using FPtr = auto(decltype(get_at_index)::*)(Tuple&) const -> Result&;
// An array of said memfun pointers, each for one known index
FPtr fptrs[sizeof...(Ts)]{&decltype(get_at_index)::template operator()<Is>...};
// Invoke the correct pointer
return (get_at_index.*(fptrs[i]))(t);
}(std::index_sequence_for<Ts...>());
}
int main() {
std::tuple<std::string, int, double, std::string> t{"abc", 2, 5.9, "def"};
for (int i = 0; i < 4; ++i) {
try {
std::string& s = runtime_get<std::string>(i, t);
std::cout << "Success: " << s << '\n';
} catch (const std::runtime_error& ex) {
std::cout << "Failure: " << ex.what() << '\n';
}
}
}
Success: abc
Failure: Index does not contain the right type
Failure: Index does not contain the right type
Success: def
This is just the lvalue reference version, you might need other overloads. If you want a more reusable bit of trickery for the runtime-to-compile-time index conversion that you can hide away in a header, check out the std::call proposal.
Note also that this can be built out of a callback-based solution:
template<typename Result, typename... Ts>
auto runtime_get(std::size_t i, std::tuple<Ts...>& t) -> Result& {
return callback_get(i, t, [](auto& elem) -> Result& { /* same implementation as get_at_index */ });
}
The key point is that types must be resolved at compile-time. In the case of a template as a callback, that callback is being instantiated for every possible type regardless of whether that instatiation is actually used at runtime. You end up with N different callback functions, one for each possible case the program could encounter. There's no analogue for a simple variable.
Therefore, you need to condense N possibilities down to the same behaviour. This can be done as above by choosing a specific type and throwing (or returning an empty optional) on a mismatch. This can also be done by returning a variant, which covers all possible types, but doesn't actually bring you any closer to overcoming the impossible part of this problem—std::visit uses the same callback mechanism where each possible type needs to be compiled against the given callback.
Related
Is it possible to generate new type whenever a function is called?
I've read that each lambda has its own unique type, so I've tried:
template<class T, class F> struct Tag { };
template<class T>
auto func(const T &t) -> auto
{
auto f = [] () {};
return Tag<T, decltype(f)>();
}
static_assert(!std::is_same_v<decltype(func(0)), decltype(func(1))>, "type should be different.");
But, static_assert fails.
Can I make func() return a value of different type whenever func() called regardless of type T and the value of t?
No, not when the function is called. Types are generated at compile time, not at runtime.
Have a look at the question Can the 'type' of a lambda expression be expressed? Here is a code based on an answer from there.
#include <iostream>
#include <set>
int main()
{
auto n = [](int l, int r) { return l > r; };
auto m = [](int l, int r) { return l > r; };
std::set<int, decltype(n)> s(n);
std::set<int, decltype(m)> ss(m);
std::set<int, decltype(m)> sss(m);
std::cout << (std::is_same<decltype(s), decltype(ss)>::value ? "same" : "different") << '\n';
std::cout << (std::is_same<decltype(ss), decltype(sss)>::value ? "same" : "different") << '\n';
}
Result:
different
same
C++ is a statically typed language, which means the types only exist in the source code, and there is little trace of them left in the runtime.
Lambdas are no exception - they do have unique types, but those are defined at the compile time.
Templates can indeed be used to generate new types, and that is possible because templates are evaluated at compile time and therefore only exist in the source code as well.
So the strict answer is no, you cannot generate new types when the function is called, as function calls happen in runtime.
This being said, you can achieve pretty much any desirable flexibility in C++ with some clever design, just check out some common design patterns.
template<class... Ts, class T>
constexpr auto contains(T&&){
auto types = hana::to<hana::tuple_tag>(hana::tuple_t<Ts...>);
return hana::bool_c<hana::find(types, hana::type_c<T>) != hana::nothing>;
}
auto ht = hana::make_tuple(1,2,3,'c');
auto ht1 = hana::filter(ht, [](auto t){
return contains<int,float,double>(t);
});
//prints 0
std::cout << hana::size(ht1) << std::endl;
I am not sure if I am using boost hana correctly but contains seems to work.
std::cout << contains<int,float,double>(5) << std::endl; // 1
std::cout << contains<int,float,double>('c') << std::endl; // 0
std::cout << contains<int,float,double>(5.0f) << std::endl; // 1
Why is the size of ht1 0?
The problem here actually has nothing to do with Hana, it has to do with the way universal references are deduced. Just to clear up things, hana::type_c<T> == hana::type_c<U> is precisely equivalent to std::is_same<T, U>{}. There is no reference or cv-qualifier removing when comparing hana::types. You can look at various articles (like this or this) for these rules.
Now, let me go through your code and modify some things, with comments. First,
auto types = hana::to<hana::tuple_tag>(hana::tuple_t<Ts...>);
is redundant, because you're already creating a hana::tuple with hana::tuple_t. Hence, hana::tuple_t<T...> only is sufficient. Secondly, there's this line:
return hana::bool_c<hana::find(types, hana::type_c<T>) != hana::nothing>;
Instead of checking hana::find(...) != hana::nothing, I would instead use hana::contains, which expresses your intent better and might be more optimized too. In general, and especially with a metaprogramming library with Hana, don't try to reason as to what will be faster. Just state your intent as clearly as possible and hope for me to do my job properly on the implementation side :-). Hence, you'll end up with
return hana::bool_c<hana::contains(types, hana::type_c<T>)>;
Now, that hana::bool_c<...> really is redundant, because hana::contains already returns a boolean integral_constant. Hence, the above is equivalent to the simpler
return hana::contains(types, hana::type_c<T>);
Finally, putting all the bits together and simplifying, you get
template<class... Ts, class T>
constexpr auto contains(T&&){
return hana::contains(hana::tuple_t<Ts...>, hana::type_c<T>);
}
I'm personally not a fan of taking the T&& as an argument, when all you want is actually the type of that object. Indeed, that forces you to actually provide an object to contains function, which might be unwieldy in some circumstances (what if you don't have an object around?). Furthermore, it can be confusing to be comparing values with types:
contains<int, char, double>(3.5) // wtf, 3.5 is not in [int, char, double]!
Instead, I would write the following if it was my own code:
template<class... Ts, class T>
constexpr auto contains(T type){
return hana::contains(hana::tuple_t<Ts...>, type);
}
// and then use it like
contains<int, char, double>(hana::type_c<double>)
But that is part of the interface of your function, and I guess you are a better judge than I to know what your needs are in terms of interface.
The problem is the T&&, I think it deduces the type to be of type T& which means that hana::type_c<T> != hana::type_c<T&> The fix is to leave the && because they are unnecessary.
template<class... Ts, class T>
constexpr auto contains(T){
auto types = hana::tuple_t<Ts...>;
return hana::find(types, hana::type_c<T>) != hana::nothing;
}
Just to add to your answer, your ht1 was calling contains with t an lvalue. The following demonstrates T&& in the case of passing an rvalue and an lvalue:
#include<boost/hana.hpp>
namespace hana = boost::hana;
template<class T>
void test1(T&&) {
static_assert(hana::type_c<T> == hana::type_c<int>, "");
}
int main() {
static_assert(hana::type_c<int> != hana::type_c<int&&>, "");
test1(5);
int x = 5;
test1(x); //fails
}
clang output:
main.cpp:7:3: error: static_assert failed ""
static_assert(hana::type_c<T> == hana::type_c<int>, "");
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:14:3: note: in instantiation of function template specialization 'test1<int &>' requested here
test1(x); //fails
^
1 error generated.
On the Boost mailinglist, the following clever trick to create a tuple-like entity was recently posted by #LouisDionne:
#include <iostream>
auto list = [](auto ...xs) {
return [=](auto access) { return access(xs...); };
};
auto length = [](auto xs) {
return xs([](auto ...z) { return sizeof...(z); });
};
int main()
{
std::cout << length(list(1, '2', "3")); // 3
}
Live Example.
The cleverness is that list is a lambda taking a variadic parameter-list as input, and returning a lambda as an output that will take another lambda to act on its input. Similarly, length is a lambda taking a list-like entity, to which it will supply the variadic sizeof... operator to the list's original input parameters. The sizeof... operator is wrapped inside a lambda so that it can be passed to the list.
Question: is there a name for this tuple-creation idiom? Perhaps from a functional programming language where higher-order functions are more commonly used.
I think this is a subtle implementation of a Monad-like thing, specifically something in the same spirit of the continuation monad.
Monads are a functional programming construction used to simulate state between different steps of a computation (Remember that a functional language is stateless).
What a monad does is to chain different functions, creating a "computation pipeline" where each step knows about the current state of the computation.
Monads have two primary pilars:
A return function, which takes a value and returns it in a Monad-ready form.
A bind function, which takes a Monad-ready value (From the previous pipeline step) and unwraps it to its original from to pass the value to the next step.
The Wikipedia has very good examples and explanations about monads.
Let me rewrite the given C++14 code:
auto list = []( auto... xs )
{
return [=]( auto access ) { return access(xs...); };
};
I think here we identify the return function of a monad: Takes the value and returns it in a Monadic way.
Specifically, this return returns a functor (In the mathematical sense, not a C++ functor) which goes from the "tuple" category to the variadic pack category.
auto pack_size = [](auto... xs ) { return sizeof...(xs); };
pack_size is just a normal function. It would be used in a pipeline to do some work.
auto bind = []( auto xs , auto op )
{
return xs(op);
};
And length is only a non-generic version of something near to the monad bind operator, an operator which takes a monadic value from a previous pipeline step, and bypasses it to the specified function (Function which really does the work). That function is the functionality done by this computation step.
Finally your call could be rewritten as:
auto result = bind(list(1,'2',"3"), pack_size);
So, whats the name of this tuple creation idiom? Well, I think this could be called "monad-like tuples", since its not exactly a monad, but the tuple representation and expansion works in a similar way, remaining to the Haskell continuation monad.
Edit: More fun
Just for the shake of funny C++ programming, I have followed exploring this monad-like thing. You could find some examples here.
I would call this idiom tuple-continuator or more generally, monadic-continuator. It is most definitely an instance of a continuation monad. A great introduction for continuation monad for C++ programmers is here. In essence, the list lambda above takes a value (a variadic parameter-pack) and returns a simple 'continuator' (the inner closure). This continuator, when given a callable (called access), passes the parameter pack into it and returns whatever that callable returns.
Borrowing from the FPComplete blogpost, a continuator is more or less like the following.
template<class R, class A>
struct Continuator {
virtual ~Continuator() {}
virtual R andThen(function<R(A)> access) = 0;
};
The Continuator above is abstract--does not provide an implementation. So, here is a simple one.
template<class R, class A>
struct SimpleContinuator : Continuator<R, A> {
SimpleContinuator (A x) : _x(x) {}
R andThen(function<R(A)> access) {
return access(_x);
}
A _x;
};
The SimpleContinuator accepts one value of type A and passes it on to access when andThen is called. The list lambda above is essentially the same. It is more general. Instead of a single value, the inner closure captures a parameter-pack and passes it to the access function. Neat!
Hopefully that explains what it means to be a continuator. but what does it mean to be a monad? Here is a good introduction using pictures.
I think the list lambda is also a list monad, which is implemented as a continuation monad. Note that continuation monad is the mother of all monads. I.e., you can implement any monad with a continuation monad. Of course, list monad is not out of reach.
As a parameter-pack is quite naturally a 'list' (often of heterogeneous types), it makes sense for it to work like a list/sequence monad. The list lambda above is a very interesting way of converting C++ parameter-packs to a monadic structure. Therefore, operations can be chained one after another.
The length lambda above, however, is a bit disappointing because it breaks the monad and the nested lambda inside simply returns an integer. There is arguably a better way to write the length 'getter' as shown below.
----Functor----
Before we can say the list lambda is a monad, we have to show that it is a functor. I.e., fmap must be written for list.
The list lambda above serves as the creator of the functor from a parameter pack---essentially it serves as the return. That created functor keeps the parameter-pack with itself (capture) and it allows 'access' to it provided you give a callable that accepts a variable number of arguments. Note that the callable is called EXACTLY-ONCE.
Lets write fmap for such a functor.
auto fmap = [](auto func) {
return [=](auto ...z) { return list(func(z)...); };
};
The type of the func must be (a -> b). I.e., in C++ speak,
template <class a, class b>
b func(a);
The type of fmap is fmap: (a -> b) -> list[a] -> list[b] I.e., in C++ speak,
template <class a, class b, class Func>
list<b> fmap(Func, list<a>);
I.e., fmap simply maps list-of-a to a list-of-b.
Now you can do
auto twice = [](auto i) { return 2*i; };
auto print = [](auto i) { std::cout << i << " "; return i;};
list(1, 2, 3, 4)
(fmap(twice))
(fmap(print)); // prints 2 4 6 8 on clang (g++ in reverse)
Therefore, it is a functor.
----Monad----
Now, lets try to write a flatmap (a.k.a. bind, selectmany)
Type of flatmap is flatmap: (a -> list[b]) -> list[a] -> list[b].
I.e., given a function that maps a to a list-of-b and a list-of-a, flatmap return a list-of-b. Essentially, it takes each element from list-of-a, calls func on it, receives (potentially empty) list-of-b one-by-one, then concatenates all the list-of-b, and finally returns the final list-of-b.
Here's an implementation of flatmap for list.
auto concat = [](auto l1, auto l2) {
auto access1 = [=](auto... p) {
auto access2 = [=](auto... q) {
return list(p..., q...);
};
return l2(access2);
};
return l1(access1);
};
template <class Func>
auto flatten(Func)
{
return list();
}
template <class Func, class A>
auto flatten(Func f, A a)
{
return f(a);
}
template <class Func, class A, class... B>
auto flatten(Func f, A a, B... b)
{
return concat(f(a), flatten(f, b...));
}
auto flatmap = [](auto func) {
return [func](auto... a) { return flatten(func, a...); };
};
Now you can do a lot of powerful things with a list. For example,
auto pair = [](auto i) { return list(-i, i); };
auto count = [](auto... a) { return list(sizeof...(a)); };
list(10, 20, 30)
(flatmap(pair))
(count)
(fmap(print)); // prints 6.
The count function is a monad-perserving operation because it returns a list of single element. If you really want to get the length (not wrapped in a list) you have to terminate the monadic chain and get the value as follows.
auto len = [](auto ...z) { return sizeof...(z); };
std::cout << list(10, 20, 30)
(flatmap(pair))
(len);
If done right, the collection pipeline pattern (e.g., filter, reduce) can now be applied to C++ parameter-packs. Sweet!
----Monad Laws----
Let's make sure the list monad satisfies all three monad laws.
auto to_vector = [](auto... a) { return std::vector<int> { a... }; };
auto M = list(11);
std::cout << "Monad law (left identity)\n";
assert(M(flatmap(pair))(to_vector) == pair(11)(to_vector));
std::cout << "Monad law (right identity)\n";
assert(M(flatmap(list))(to_vector) == M(to_vector));
std::cout << "Monad law (associativity)\n";
assert(M(flatmap(pair))(flatmap(pair))(to_vector) ==
M(flatmap([=](auto x) { return pair(x)(flatmap(pair)); }))(to_vector));
All asserts are satisfied.
----Collection Pipeline----
Although the above 'list' lambda is provably a monad and shares characteristics of the proverbial 'list-monad', it is quite unpleasant. Especially, because the behavior of common collection pipeline combinators, such as filter (a.k.a where) does not meet common expectations.
The reason is just how C++ lambdas work. Each lambda expression produces a function object of a unique type. Therefore, list(1,2,3) produces a type that has nothing to do with list(1) and an empty list, which in this case would be list().
The straight-forward implementation of where fails compilation because in C++ a function can not return two different types.
auto where_broken = [](auto func) {
return flatmap([func](auto i) {
return func(i)? list(i) : list(); // broken :-(
});
};
In the above implementation, func returns a boolean. It's a predicate that says true or false for each element. The ?: operator does not compile.
So, a different trick can be used to allow continuation of the collection pipeline. Instead of actually filtering the elements, they are simply flagged as such---and that's what makes it unpleasant.
auto where_unpleasant = [](auto func) {
return [=](auto... i) {
return list(std::make_pair(func(i), i)...);
};
};
The where_unpleasant gets the job done but unpleasantly...
For example, this is how you can filter negative elements.
auto positive = [](auto i) { return i >= 0; };
auto pair_print = [](auto pair) {
if(pair.first)
std::cout << pair.second << " ";
return pair;
};
list(10, 20)
(flatmap(pair))
(where_unpleasant(positive))
(fmap(pair_print)); // prints 10 and 20 in some order
----Heterogeneous Tuples----
So far the discussion was about homogeneous tuples. Now lets generalize it to true tuples. However, fmap, flatmap, where take only one callback lambda. To provide multiple lambdas each working on one type, we can overload them. For example,
template <class A, class... B>
struct overload : overload<A>, overload<B...> {
overload(A a, B... b)
: overload<A>(a), overload<B...>(b...)
{}
using overload<A>::operator ();
using overload<B...>::operator ();
};
template <class A>
struct overload<A> : A{
overload(A a)
: A(a) {}
using A::operator();
};
template <class... F>
auto make_overload(F... f) {
return overload<F...>(f...);
}
auto test =
make_overload([](int i) { std::cout << "int = " << i << std::endl; },
[](double d) { std::cout << "double = " << d << std::endl; });
test(10); // int
test(9.99); // double
Let's use the overloaded lambda technique to process a heterogeneous tuple continuator.
auto int_or_string =
make_overload([](int i) { return 5*i; },
[](std::string s) { return s+s; });
list(10, "20")
(fmap(int_or_string))
(fmap(print)); // prints 2020 and 50 in some order
Finally, Live Example
This looks like a form of continuation passing style.
The rough idea of CPS is this: instead of having a function (say f) return some value, you give to f another argument, which is a function, called a continuation. Then, f calls this continuation with the return value instead of returning. Let's take an example:
int f (int x) { return x + 42; }
becomes
void f (int x, auto cont) { cont (x + 42); }
The call is a tail call, and can be optimized into a jump (this is why TCO is mandated in some languages, like Scheme, whose semantics rely on some form of transformation into CPS).
Another example:
void get_int (auto cont) { cont (10); }
void print_int (int x) { printf ("%d", x), }
You can now do get_int (std::bind (f, _1, print_int)) to print 54. Note that all the continuation calls are always tail calls (the call to printf is also a continuation call).
A well-known example is asynchronous callbacks (AJAX calls in javascript for instance): you pass a continuation to a routine executing in parallel.
Continuations can be composed (and form a monad, in case you're interested), as in the above example. In fact it is possible to transform a (functional) program entirely into CPS, so that every call is a tail call (and then you need no stack to run the program !).
Are there any benefits of overloading a method/function to take true_type or false_type parameter, compared to using one if statement?
I see more and more code using overloaded methods with true_type and false_type parameters.
A short example using if statement
void coutResult(bool match)
{
if (match)
cout << "Success." << endl;
else
cout << "Failure." << endl;
}
bool match = my_list.contains(my_value);
coutResult(match);
Compared to using an overloaded function:
void coutResult(true_type)
{
cout << "Success." << endl;
}
void coutResult(false_type)
{
cout << "Failure." << endl;
}
bool match = my_list.contains(my_value);
coutResult(match);
Your second example code wouldn't compile, which is a symptom of the difference between compile-time overload resolution and run-time conditional branching to "choose" which code to execute.
"Overloading a function to take true_type or false_type parameter" allows to make this choice at compile time if the decision only depends on types and compile-time constants.
"Using an if check" is necessary if the choice can't be done until run time when some variable values are known.
In your example the bool match = my_list.contains(my_value) is obviously not known before running the program, so you can't use overloading.
But the difference is most important for templates, where the choice is not only "which path to execute" but "which code to instantiate and compile and then call". The code from your linked video is rather in this spirit:
Consider this (wrong) code (omitting #includes and std::s for brevity):
template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
// Make code shorter
typedef typename iterator_traits<InIt>::difference_type Diff;
typedef typename iterator_traits<InIt>::iterator_category Tag;
// Choice
if (is_same<Tag, random_access_iterator_tag>::value)
{
return last - first;
}
else
{
Diff n = 0;
while (first != last) {
++first;
++n;
}
return n;
}
}
There are at least two problems here:
If you try to call it with iterators that are not random-access (e.g. std::list<T>::iterator), it will actually fail to compile (with an error pointing the line return last - first;). The compiler has to instantiate and compile the entire function body, including both the if and the else branches (even though only one is to be executed), and the expression last - first is invalid for non-RA iterators.
Even if that compiled, we would be doing a test at run time (with the related overhead) for a condition that we could have tested as soon as compile time, and compiling unneeded code parts. (The compiler may be able to optimize that, but that's the concept.)
To fix it you can do:
// (assume needed declarations...)
template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
// Make code shorter
typedef typename iterator_traits<InIt>::iterator_category Tag;
// Choice
return distanceImpl(first, last, is_same<Tag, random_access_iterator_tag>());
}
template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, true_type)
{
return last - first;
}
template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, false_type)
{
// Make code shorter
typedef typename iterator_traits<InIt>::difference_type Diff;
Diff n = 0;
while (first != last) {
++first;
++n;
}
return n;
}
or alternatively (possible here) with types directly:
/* snip */
distance(InIt first, InIt last)
{
/* snip */
return distanceImpl(first, last, Tag());
}
/* snip */
distanceImpl(InIt first, InIt last, random_access_iterator_tag)
{
return last - first;
}
/* snip */
distanceImpl(InIt first, InIt last, input_iterator_tag)
{
/* snip */
Diff n = 0;
/* snip */
return n;
}
Now only the "correct" distanceImpl will be instantiated and called (the choice being done at compile time).
This works because the types (e.g. InIt or Tag) are known at compile-time, and is_same<Tag, random_access_iterator_tag>::value is a constant that is known at compile-time too. The compiler can resolve which overload is to be called, only based on types (that's overload resolution).
Note: Even though the "tags" are passed by value, they are only used as unnamed, unused parameters for "dispatch" (their value is not used, only their type) and the compiler can optimize them out.
You can also read Item 47: Use traits classes for information about types from Scott Meyers' Effective C++, Third Edition.
Such overloading is useful for compile-time optimizations. Note that in the example you are referring to a typedef is used to define a type which matches std::true_type or std::false_type. This type is evaluated in compile time. Creating a value of the type in a subsequent function call is necessary just to call the function: you cannot call a function with a type as an argument.
You cannot perform overloading based on a value of a variable. Overloading is performed based on type.
The code
bool match = my_list.contains(my_value);
coutResult(match);
doesn't compile as there is no coutResult(bool) function:
error: no matching function for call to ‘coutResult(bool)’
note: candidates are: void coutResult(std::true_type)
note: void coutResult(std::false_type)
So, if your expression can be evaluated in compile-time, you may benefit from overloading functions for true_type and false_type thus removing additional checks in run-time. But if the expression is not a constant, you must use if.
In C++, generally you can not explicitly derive the object type because of the lack of Reflection property. If you want to operate according to object type, you need such kind of function overloading.
If you want to use If statement, you need to define some extra member or method for each related class to get class information which may not be permitted for some contexts and it introduces extra overhead ofcourse. As a more practical way, typeid can be used instead of this extra member and the object's id can be compared with the intended class id at run-time.
Moreover, If the classes are quite different from each other (like not having a common base) and they are defined and designed for entirely separate pusposes, it is a good design practice to handle them in separate methods instead of a single conditional.
This question already has answers here:
Is there a way to pass auto as an argument in C++?
(4 answers)
auto parameter type in functions
(1 answer)
Closed 4 months ago.
Is it possible to create a generic C++ function foo?
foo(Object bar, Object fred)
{
//code
}
in which that if the two objects are recognized, they are compared and a comparison value is returned otherwise some other value is returned to indicate a comparison was not possible?
I ask in the case of genericizing a sorting class, in which case you can use this method, and when you derive new objects you want to sort, you add to this foo function, a method on which to sort the new type of Object.
Using templates, define two versions of the function, one where the parameters are the same type and one where they can be different:
#include <string>
#include <iostream>
using namespace std;
template<typename Type>
void func(Type, Type)
{
cout << "same" << endl;
}
template<typename TypeA, typename TypeO>
void func(TypeA, TypeO)
{
cout << "different" << endl;
}
int main()
{
func(5, 3); // same
func(5, 3.0); // different
func(string("hello"), "hello"); // different
func(5.0, 3.0); // same
return 0;
}
Output:
same
different
different
same
I think you are in dire need of Templates!
You can write a template function and then write a specialization for the said types to do something specific if the need be.
template<class Type1, class Type2>
void foo(Type1 t1, Type2 t2)
{
// put code here for function
}
call as
foo<std::string, int> ("hello", 10);
Most probably you need to use templates as other people suggest:
template <class T>
return_type func(T const& l, T const& r)
{
...
}
Because you normally want compilation to fail when the operation implemented by a generic function does not make sense for particular types, so you would either use conditional definition (in the below example is_arithmetic):
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_arithmetic.hpp>
template <class T>
typename boost::enable_if<boost::is_arithmetic<T>, return_type>::type
func(T const& l, T const& r)
{
...
}
or static assertion in the code to yield the same result:
#include <boost/type_traits/is_arithmetic.hpp>
template <class T>
return_type func(T const& l, T const& r)
{
static_assert(boost::is_arithmetic<T>::type::value, "incompatible types");
...
}
I'm going to stick my neck out here and say you don't need Templates to do this. I'm not saying don't use them, but just that depending on exactly what you're wanting to do, there are alternatives.
What it sounds like you want is the ability to compare two generic objects provided that they adhere to a common set of ground rules. You could actually implement this using traditional inheritance or using templates. The choice of which you want comes down to how flexible you need it to be and whether you want some of the decisions to be made at runtime or compile time. If the latter - i.e. you want to pick up on casting errors etc., - then go for templates.
Either way, your objects will either have to adhere to some basic groundrules for how you compare them and preferably encapsulate that - this way your comparitor would be generic. or you'd have to write different comparitors for each object comparison. While it sounds like the latter is what you want, be wary of letting too much of your class implementation leach out into the comparitor function and thereby breaking encapsulation.
From my own experience, going straight to the template approach can occasionally result in a lot of bloated, messed up code which is hard to read, debug and maintain. Take a hard look at you design and what you actually need first.
OP seems to want to know if the 2 objects are comparable or not. You can use template specialization to achieve this (note: this doesn't compile on VC 10, but does on g++ 4.7). The only nuance, is you want this function to
they are compared and a comparison value is returned otherwise some other value is returned to indicate a comparison was not possible
But you need to define some sort of structure to signify that a comparison was not possible; using a magic number '-500' or whatever is not good style. Alternately, you could throw an error, and allow it to be caught an handled.
struct NoCompare{};
template <typename U1, typename U2>
static auto compare2(const U1 & u1, const U2 & u2) -> decltype(u1 == u2)
{
cout << "Comparable" << endl;
return u1 == u2;
}
static int compare2(...)
{
// Comparison not supported - return whatever value you want. (change the return type as appropriate)
cout << "Not comparable" << endl;
return -500;
}
int main()
{
int a = 5, b = 3, c = 3;
NoCompare dns;
cout << compare2(a, b) << endl;
cout << compare2(dns, b) << endl;
cout << compare2(c, b) << endl;
return 0;
}
Output:
C:\MinGW\MinGW>a
Comparable
0
Not comparable
-500
Comparable
1
It seems that you are referring to Common Lisp / CLOS -style generic functions which do multiple dynamic dispatch. C++ does single dynamic dispatch with methods but only single static dispatch with functions. So the answer is no. C++ doesn't support this at the moment. There have been proposals along the years to add it into the language but that hasn't happened yet.