Reputation: 362
Despite, the fact, we have std::max
, I wanted to try if it is possible to make a Max
version that takes variadic arguments and calls the Max
recursively for finding the max element.
I saw similar posts in stack overflow, but those are old and most of them use std::max
inside. Since I have a specific error and using a newer compiler, this post is not duplicated easily.
Following is the code I have written:
#include <iostream>
#include <string>
#include <format>
using namespace std::string_literals;
template <typename T>
constexpr T Max(T&& value)
{
return value;
}
template <typename T, typename... Ts>
constexpr T Max(T&& value, Ts&&... args)
{
const T maxRest = Max(args...);
return (value > maxRest) ? value : maxRest;
}
int main()
{
std::cout << std::format("Maximum integer: {}\n", Max(1));
std::cout << std::format("Maximum integer: {}\n", Max(5, 2, 10, 6, 8));
std::cout << std::format("Maximum integer: {}\n", Max("string1", "string2"s)); // error in this line!!
std::cout << std::format("Maximum double: {}\n", Max(3.14, 1.23, 2.56, 0.98));
return 0;
}
For which I am getting:
main.cc(79, 21) : error C2440 : 'initializing' : cannot convert from 'std::string' to 'const char (&)[8]'
main.cc(79, 21) : message: Reason: cannot convert from 'std::string' to 'const char [8]'
main.cc(79, 21) : message: No user - defined - conversion operator available that can perform this conversion, or the operator cannot be called
main.cc(87, 55) : message: see reference to function template instantiation 'T Max<const char(&)[8],std::string>(T,std::string &&)' being compiled
with
[
T = const char(&)[8]
]
Max("string1", "string2"s));
. I do not know, how can resolve this.Max
function in c++20. Does anybody have any suggestion to
make the two Max
functions into one?Upvotes: 9
Views: 476
Reputation: 32952
Likewise, I am also feeling that I am writing more to achieve this
Max
function in c++20 [...]?
As extension to the other answer, using fold expressions, the Max
can be made non-recursive as well.
#include <type_traits> // std::common_type, std::remove_cvref
#include <functional> // std::greater
template<typename... T> // common type helper
using CommonType = std::common_type_t<std::remove_cvref_t<T>...>;
constexpr auto Max(auto const& value, auto const&... args)
{
CommonType<decltype(value), decltype(args)...> maxVal = value;
return sizeof...(args) == 0u ? maxVal
: (((maxVal = std::greater{}(args, maxVal) ? args : maxVal), ...)
, maxVal);
}
However, for the case of consecutive string literals, that requires some addition:
template<typename... T> // common type helper
using CommonType = std::common_type_t<std::remove_cvref_t<T>...>;
// For string literals comparison.
constexpr auto handleStrLiterals(auto const& t)
{
if constexpr (std::is_convertible_v<decltype(t), std::string_view>)
return std::string_view{ t };
else return t;
};
constexpr auto Max(auto const& value, auto const&... args)
{
CommonType<decltype(handleStrLiterals(value)), decltype(args)...>
maxVal = handleStrLiterals(value);
return sizeof...(args) == 0u ? maxVal
: (((maxVal = std::greater{}(args, maxVal) ? args : maxVal), ...)
, maxVal);
}
Upvotes: 4
Reputation: 32952
Likewise, I am also feeling that I am writing more to achieve this
Max
function in c++20 [...] ?
Your Max
function can maximize the simplicity by
Having abbreviated function template (Since c++20) declaration, and
The compile time branching with if constexpr
(since c++17).
constexpr auto Max(auto const& value, auto const&... args)
{
if constexpr (sizeof...(args) == 0u) // Single argument case!
return value;
else // For the Ts...
{
const auto max = Max(args...);
return value > max ? value : max;
}
}
Update: As pointed out in the comment section by @TedLyngmo, the above does not work if you only pass consecutive const char*
s (string literals). Ex. scenario
Max("string1"s, "string2", "string4", "string3") // result is "string2" instead of "string4"
Because this is resulting a pointer comparison rather than the comparison you want. It was the case also for your original shown code. You might want to handle this situation separately.
For instance, in the following code example, if the value
is convertible to std::string_view
, we convert it to std::string_view
and do the greater check:
#include <type_traits> // std::is_convertible
constexpr auto Max(auto const& value, auto const&... args)
{
if constexpr (sizeof...(args) == 0u) // Single argument case!
{
if constexpr (std::is_convertible_v<decltype(value), std::string_view>)
return std::string_view{value};
else
return value;
}
else // For the Ts...
{
const auto max = Max(args...);
return value > max ? value: max;
}
}
Likewise, each time when you use this Max
function, always remember to check whether the passed argument is some type of pointer, which is clearly not handled by it.
I think the error is coming from the function call:
Max("string1", "string2"s));
. I do not know, how can to resolve this.
When you call Max("string1", "string2"s))
, the compiler deduces the T
(i.e. return type), to be
const char[8]
, that is the type of the first argument(i.e. "string1"
). However, the second argument is a std::string
(i.e. "string2"s
). Now for the line :
const T maxRest = Max(args...);
this std::string
must be now implicitly convertible to const char [8]
.This is not viable, and hence the compiler produces a type mismatch error.
To fix the issue, you can simply let the compiler deduce the type for you; That means, instead of defining or assuming the
the return type will be always T
, use auto
so that compiler can deduce the type for you.
template <typename T, typename... Ts>
constexpr auto Max(T const& value, Ts const&... args)
// ^~~~ ---> Simply 'auto'
{
const auto maxRest = Max(args...);
// ^~~~ ---> Simply 'auto'
return (value > maxRest) ? value : maxRest;
}
Alternatively, you could also use the std::common_type_t
for defining the return type.
#include <type_traits> // std::common_type_t
template <typename T, typename... Ts>
constexpr auto Max(T const& value, Ts const&... args)
-> std::common_type_t<T, Ts...>
{
// ....
}
Upvotes: 13