If you have access to a recent version of boost, you can use the incredibly powerful HOF library (stands for higher order functions).
One of the functions I use most to simplify code path selection based on argument type is the function first_of
.
The way this works is that you give it a list of template function objects (or lambdas) in the order you want the compiler to try them. The first legal function object in the list is selected.
example:
#include <cstddef>
#include <boost/hof.hpp>
#include <cstring>
#include <utility>
#include <iostream>
// a function to compute length from a pointer. For exposition,
// I have only considered char pointers but any number of overloads will work.
template<class T>
std::size_t
string_pointer_length(T*p)
{
// for exposition
return std::strlen(p);
}
// a function to compute string length from a literal
template<class T, std::size_t N>
constexpr
std::size_t literal_string_length(T (&s)[N])
{
return N - 1;
}
// The generic GetLength function which takes any kind of string
template <typename T>
std::size_t GetLength(T&& str)
{
// select the FIRST legal choice of the following lambdas and invoke...
return boost::hof::first_of(
[](auto&&s) BOOST_HOF_RETURNS(literal_string_length(s)),
[](auto&&s) BOOST_HOF_RETURNS(string_pointer_length(s))
)(str);
}
int main()
{
static const auto lit = "hello";
auto plit = std::addressof(lit[0]);
auto n = GetLength(lit);
auto n2 = GetLength(plit);
std::cout << n << ", " << n2 << std::endl;
}
The macro BOOST_HOF_RETURNS
saves us having to spell out the lambdas like this:
return boost::hof::first_of(
[](auto&&s) -> decltype(literal_string_length(s)) { return literal_string_length(s); },
[](auto&&s) BOOST_HOF_RETURNS(string_pointer_length(s))
)(str);
If you're not able to use boost.hof, writing our own replacement is surprisingly trivial:
#include <cstddef>
#include <cstring>
#include <tuple>
#include <utility>
#include <iostream>
template<class T>
std::size_t
string_pointer_length(T*p)
{
// for exposition
return std::strlen(p);
}
template<class T, std::size_t N>
constexpr
std::size_t literal_string_length(T (&s)[N])
{
return N - 1;
}
template<class...Args, class This, class...Others>
constexpr auto try_these(std::tuple<Args...> args, This _this, Others...others)
{
if constexpr (std::is_invocable_v<This, Args...>)
{
return std::apply(_this, args);
}
else
{
return try_these(args, others...);
}
}
struct invoke_string_pointer_length
{
template<class S>
constexpr auto operator()(S&& s) const -> decltype(string_pointer_length(s))
{ return string_pointer_length(s); }
};
struct invoke_literal_string_length
{
template<class S>
constexpr auto operator()(S&& s) const -> decltype(literal_string_length(s))
{ return literal_string_length(s); }
};
template <typename T>
std::size_t GetLength(T&& str)
{
return try_these(std::forward_as_tuple(std::forward<T>(str)),
invoke_literal_string_length(),
invoke_string_pointer_length());
}
int main()
{
static const auto lit = "hello";
auto plit = std::addressof(lit[0]);
auto n = GetLength(lit);
auto n2 = GetLength(plit);
std::cout << n << ", " << n2 << std::endl;
}