Please, consider the following example (tag dispatching, variadic template, perfect forwarding, and so on, all in one):
#include <iostream>
#include <utility>
#include <string>
struct A { };
struct B { };
void doIt(A&&, const std::string &) {
std::cout << "A-spec" << std::endl;
}
template<typename T, typename... Args>
void doIt(T&&, Args&&...) {
std::cout << "template" << std::endl;
}
template<typename T, typename... Args>
void fn(Args&&... args) {
doIt(T{}, std::forward<Args>(args)...);
}
int main() {
const std::string foo = "foo";
std::string bar = "bar";
fn<A>(foo);
fn<A>(bar);
fn<B>(foo);
}
In that case, the output is:
A-spec
template
template
The reason is quite obvious, it doesn't bother me.
What I'd like to achieve is to call the first instance of the doIt
function in both the cases, no matter if the string has the const specifier or not.
Of course, a possible solution is to define a new doIt
with the right prototype, anyway I'd like to know if there is also another solution.
So far, I've tried to get it by means of add_const
, but I'm quite sure I missed something.
Is there any viable solution to add silently the const specifier and get it working?
EDIT
I've updated the example above, in order to be more coherent with the real problem.
Moreover, despite the interesting answers, I forgot to cite that this is only a reduced example, so the real problem does not involve only std::string
. Instead, it could happen that (as an example) for tag A
the arguments are int
and const std::string &
, while for tag B
the arguments are float
, instance of class C
, and so on.
Because of that, those answers that try to solve the problem using somehow the std::string
type won't solve the real problem, I'm sorry.
Introduce two separate functions, so that they won't collide with each other and the compiler won't raise any ambiguity error:
void doIt(A&&, const std::string &)
{
std::cout << "A-spec" << std::endl;
}
template <typename T, typename... Args>
void doIt_template(T&&, Args&&...)
{
std::cout << "template" << std::endl;
}
Prioritize two additional overloads; the preferred one is that which tries to call a specialized, non-templated version of a target function:
template <typename T, typename... Args>
auto fn_impl(int, Args&&... args)
-> decltype(doIt(T{}, std::forward<Args>(args)...), void())
{
doIt(T{}, std::forward<Args>(args)...);
}
template <typename T, typename... Args>
void fn_impl(char, Args&&... args)
{
doIt_template(T{}, std::forward<Args>(args)...);
}
Introduce a single, general dispatcher:
template <typename T, typename... Args>
void fn(Args&&... args)
{
fn_impl<T>(0, std::forward<Args>(args)...);
}
DEMO
Overloading on forwarding references is a pain, as they are very, very greedy.
One option would be to disable the template version if Args...
is a single argument convertible to std::string
:
template<typename... Args>
struct convertible_to_single_string {
static std::true_type help (std::string);
static std::false_type help (...);
using type = decltype(help(std::declval<Args>()...));
static constexpr auto value = type::value;
};
template<typename... Args>
std::enable_if_t<!convertible_to_single_string<Args...>::value, void>
doIt(int, Args&&...) {
std::cout << "template" << std::endl;
}
This would also call the string version for const char*
or anything with a user-defined conversion to std::string
. If you don't want that, there are other possibilities, like removing all references and cv-qualifiers from the type and checking if it std::is_same<std::string, T>
.
Live Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With