The following get
function will return an array of strings using the variadic arguments. It also handles the case with providing integers by simply converting them.
I provided two versions of this conversion function, one being a lambda and the other as a free function.
Can some please provide an explanation to why using a lambda fails to compile while a seemingly identical free function works fine. What changes would be required to make the lambda case work?
#include <iostream>
#include <string>
#include <vector>
#include <ranges>
#include <format>
#include <array>
#include <type_traits>
// free function converter
template <typename KEY>
auto convert_to_string_view(const KEY& key)
{
if constexpr (std::is_integral_v<KEY> && !std::is_same_v<KEY, bool>)
return std::to_string(key);
else
return key;
}
// make an array out of varargs
template <typename T, typename... Args>
inline std::array<std::common_type_t<Args...>, sizeof...(Args)>
make_array_from_vars(const Args&... args)
requires (std::is_constructible_v<std::common_type_t<Args...>, Args> && ...)
{
return { args... };
}
template<typename... KEYS>
auto get(KEYS... keys) {
// lambda
auto convert_to_sv = [](const auto& key){
if constexpr (std::is_integral_v<decltype(key)> && !std::is_same_v<decltype(key), bool>) {
return std::to_string(key);
}
else {
return key;
}
};
return make_array_from_vars<std::string_view>(convert_to_string_view(keys)...);
// using the lambda
//return make_array_from_vars<std::string_view>(convert_to_sv(keys)...); FAILS
}
int main() {
auto d = std::string("blablabla");
auto tuple_range = get(d.append("!asdsads"), "blas", 0, 0);
std::cout << tuple_range[0] << std::endl;
return 0;
}
The specific issue here is that your function template and your lambda are checking different things:
template <typename KEY>
auto convert_to_string_view(const KEY& key)
{
if constexpr (std::is_integral_v<KEY> && !std::is_same_v<KEY, bool>)
vs
auto convert_to_sv = [](const auto& key){
if constexpr (std::is_integral_v<decltype(key)> && !std::is_same_v<decltype(key), bool>) {
The function template is checking whether the type parameter KEY
is integral and not bool
. The lambda is doing the same check for decltype(key)
.
Now, considering calling both with an int
. The function template deduces KEY=int
and checks int
. The lambda implicitly deduces int
as well, but decltype(key)
isn't int
- it's int const&
, because that's the type of the variable. Indeed, integral_v<decltype(key)>
is never true - because references aren't integral.
To make them line up, you can introduce a template parameter and then do the same check:
auto convert_to_sv = []<class K>(K const& key){
if constexpr (std::is_integral_v<K> && !std::is_same_v<K, bool>) {
This would now have identical meaning.
In general the abbreviated function template syntax is only useful if you really actually don't need the type of the variable. Once you start needing it - getting it using decltype
is now longer than writing a normal function template declaration to begin with.