英文:
templated lambda return type
问题
以下是您提供的代码的翻译部分:
我想编写一小段代码,将枚举映射到结构体的特定成员。
问题在于结构体的成员具有不同的类型,如果它们没有不同,我会使用`std::array`的引用或指针来实现这个目标。
因此,我考虑使用lambda函数,因为它们现在具有模板参数。
以下是类似于我所拥有的代码:
#include <type_traits>
#include <iostream>
struct foo
{
int x;
double y;
};
enum class keys
{
x,
y,
};
void print(foo f)
{
auto get = [&f]<keys K, typename T>(std::integral_constant<keys, K>) -> T&
{
switch(K)
{
case keys::x: return f.x;
case keys::y: return f.y;
}
};
std::cout << get(std::integral_constant<keys, keys::x>{}) <<std::endl;
std::cout << get(std::integral_constant<keys, keys::y>{}) <<std::endl;
}
int main(void)
{
foo m{1,2.0};
print(m);
return 0;
}
请注意,在我的实际代码中,我无法修改foo
结构体,并且类型比int
或double
复杂得多。
但是这段代码会出现如下错误:
<source>: 在函数 'void print(foo)' 中:
<source>:26:21: 错误:无法匹配调用 '(print(foo)::<lambda(std::integral_constant<keys, K>)>) (std::integral_constant<keys, keys::x>)'
26 | std::cout << get(std::integral_constant<keys, keys::x>{}) <<std::endl;
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:18:16: 注意:候选函数: 'template<keys K, class T> print(foo)::<lambda(std::integral_constant<keys, K>)>'
18 | auto get = [&f]<keys K, typename T>(std::integral_constant<keys, K>) -> T&
| ^
<source>:18:16: 注意:模板参数推断/替换失败:
<source>:26:21: 注意:无法推断模板参数 'T'
26 | std::cout << get(std::integral_constant<keys, keys::x>{}) <<std::endl;
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:27:21: 错误:无法匹配调用 '(print(foo)::<lambda(std::integral_constant<keys, K>)>) (std::integral_constant<keys, keys::y>)'
27 | std::cout << get(std::integral_constant<keys, keys::y>{}) <<std::endl;
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:18:16: 注意:候选函数: 'template<keys K, class T> print(foo)::<lambda(std::integral_constant<keys, K>)>'
18 | auto get = [&f]<keys K, typename T>(std::integral_constant<keys, K>) -> T&
| ^
<source>:18:16: 注意:模板参数推断/替换失败:
<source>:27:21: 注意:无法推断模板参数 'T'
27 | std::cout << get(std::integral_constant<keys, keys::y>{}) <<std::endl;
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compiler returned: 1
如何解决这个问题?我有点困惑编译器为何无法推断T
。一种解决方法是通过特化,但我无法弄清楚如何为特定的模板参数专门化一个lambda函数。
英文:
I want to write a little bit of code that maps enums to specific members of a struct.
The trouble is the members of the struct have different types, if they didn't I've just used an std::array
of references or pointers to do this.
So I thought to use a lambda instead since they now have template params
Here's something like what I have:
#include <type_traits>
#include <iostream>
struct foo
{
int x;
double y;
};
enum class keys
{
x,
y,
};
void print(foo f)
{
auto get = [&f]<keys K, typename T>(std::integral_constant<keys, K>) -> T&
{
switch(K)
{
case keys::x: return f.x;
case keys::y: return f.y;
}
};
std::cout << get(std::integral_constant<keys, keys::x>{}) <<std::endl;
std::cout << get(std::integral_constant<keys, keys::y>{}) <<std::endl;
}
int main(void)
{
foo m{1,2.0};
print(m);
return 0;
}
Note, in my real code, I cannot modify the foo
struct, and the types are much more complex than int
or double
.
But this code gives me errors like:
<source>: In function 'void print(foo)':
<source>:26:21: error: no match for call to '(print(foo)::<lambda(std::integral_constant<keys, K>)>) (std::integral_constant<keys, keys::x>)'
26 | std::cout << get(std::integral_constant<keys, keys::x>{}) <<std::endl;
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:18:16: note: candidate: 'template<keys K, class T> print(foo)::<lambda(std::integral_constant<keys, K>)>'
18 | auto get = [&f]<keys K, typename T>(std::integral_constant<keys, K>) -> T&
| ^
<source>:18:16: note: template argument deduction/substitution failed:
<source>:26:21: note: couldn't deduce template parameter 'T'
26 | std::cout << get(std::integral_constant<keys, keys::x>{}) <<std::endl;
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:27:21: error: no match for call to '(print(foo)::<lambda(std::integral_constant<keys, K>)>) (std::integral_constant<keys, keys::y>)'
27 | std::cout << get(std::integral_constant<keys, keys::y>{}) <<std::endl;
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:18:16: note: candidate: 'template<keys K, class T> print(foo)::<lambda(std::integral_constant<keys, K>)>'
18 | auto get = [&f]<keys K, typename T>(std::integral_constant<keys, K>) -> T&
| ^
<source>:18:16: note: template argument deduction/substitution failed:
<source>:27:21: note: couldn't deduce template parameter 'T'
27 | std::cout << get(std::integral_constant<keys, keys::y>{}) <<std::endl;
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compiler returned: 1
How can I work around this? I'm a little confused how the compiler fails to be able to deduce T&
. One way I could work around this is via specializations but I was not able to figure out how to specialize a lambda function for specific template params.
答案1
得分: 1
你没有向 Lambda 传递任何内容,这使得编译器无法推断 T
应该是什么。正如@molbdnilo提到的,你的 switch
直到运行时才被评估,它为每个 K
的值返回不同的类型,因此编译器无法在编译时使用它来推断 T
。
只需使用返回类型的 auto
,然后使用 if constexpr
让编译器在编译时评估条件并删除未使用的代码,从而让 auto
推断实际使用的 return
的类型。
例如:
#include <type_traits>
#include <iostream>
struct foo
{
int x;
double y;
};
enum class keys
{
x,
y,
};
template<keys K>
inline constexpr bool always_false_v = false;
void print(const foo& f)
{
auto get = [&f]<keys K>() -> auto&
{
if constexpr (K == keys::x)
return f.x;
else if constexpr (K == keys::y)
return f.y;
else
static_assert(always_false_v<K>, "Invalid key");
};
std::cout << get.operator()<keys::x>() << std::endl;
std::cout << get.operator()<keys::y>() << std::endl;
}
int main(void)
{
foo m{1,2.0};
print(m);
return 0;
}
或者,正如@n.m.建议的,你可以摆脱 keys
枚举并改用成员指针,例如:
#include <type_traits>
#include <iostream>
struct foo
{
int x;
double y;
};
void print(const foo& f)
{
auto get = [&f](auto member) -> auto&
{
return f.*member;
};
std::cout << get(&foo::x) << std::endl;
std::cout << get(&foo::y) << std::endl;
}
int main(void)
{
foo m{1,2.0};
print(m);
return 0;
}
英文:
You are not passing anything to the lambda that lets the compiler deduce what T
should be. As @molbdnilo mentioned, your switch
is not evaluated until runtime, and it returns different types for each value of K
, so the compiler can't use that to deduce T
at compile-time.
Just use auto
instead for the return type, and then use if constexpr
to let the compiler evaluate the conditions at compile-time and remove unused code, thus letting auto
to deduce the type of whichever return
is actually used.
For example:
#include <type_traits>
#include <iostream>
struct foo
{
int x;
double y;
};
enum class keys
{
x,
y,
};
// see https://devblogs.microsoft.com/oldnewthing/20200311-00/?p=103553
// for why this is needed below...
template<keys K>
inline constexpr bool always_false_v = false;
void print(const foo& f)
{
auto get = [&f]<keys K>() -> auto&
{
if constexpr (K == keys::x)
return f.x;
else if constexpr (K == keys::y)
return f.y;
else
static_assert(always_false_v<K>, "Invalid key");
};
std::cout << get.operator()<keys::x>() << std::endl;
std::cout << get.operator()<keys::y>() << std::endl;
}
int main(void)
{
foo m{1,2.0};
print(m);
return 0;
}
Alternatively, as @n.m. suggested, you can just get rid of the keys
enum and use pointer-to-members instead, eg:
include <type_traits>
#include <iostream>
struct foo
{
int x;
double y;
};
void print(const foo& f)
{
auto get = [&f](auto member) -> auto&
{
return f.*member;
};
std::cout << get(&foo::x) << std::endl;
std::cout << get(&foo::y) << std::endl;
}
int main(void)
{
foo m{1,2.0};
print(m);
return 0;
}
答案2
得分: 0
你可以使用类模板来进行映射:
template <keys> struct key_to_foo_mem;
template <> struct key_to_foo_mem<keys::x> { auto& operator()(const foo& f){ return f.x; } };
template <> struct key_to_foo_mem<keys::y> { auto& operator()(const foo& f){ return f.y; } };
void print(foo f)
{
std::cout << key_to_foo_mem<keys::x>()(f) << std::endl;
std::cout << key_to_foo_mem<keys::y>()(f) << std::endl;
}
你的lambda方法有两个问题:T
被使用,但没有明确提供也没有被推断。返回类型从这两种情况中不一致地推断出,但一个函数只能有一个返回类型。此外,你使用了 T&
的尾返回类型,这会抑制编译器对返回类型的推断。
你可以移除 T
并使用 constexpr if
:
void print(foo f)
{
auto get = [&f]<keys K>(std::integral_constant<keys, K>)
{
if constexpr(K == keys::x) { return f.x; }
else { return f.x; }
};
std::cout << get(std::integral_constant<keys, keys::x>{}) << std::endl;
std::cout << get(std::integral_constant<keys, keys::y>{}) << std::endl;
}
我有点困惑编译器如何无法推断出 T&。
当你使用尾返回类型时,你在告诉编译器“这是类型”(即你抑制了推断),但 -> T&
作为尾返回类型没有意义。一旦你使用 constexpr if
,编译器可以自行推断返回类型(lambda 有隐式的 auto
返回类型)。
一种解决方法是通过特化来解决,但我无法弄清楚如何为特定模板参数特化lambda函数。
重要的是要理解,对于上面的lambda表达式,被模板化的是 operator()
,而不是lambda的类型。如何使用特化我已经在上面展示过(尽管这是对类模板的特化,而不是对成员模板的特化)。
英文:
You can use a class template for the mapping:
template <keys> struct key_to_foo_mem;
template <> struct key_to_foo_mem<keys::x> { auto& operator()(const foo& f){ return f.x; } };
template <> struct key_to_foo_mem<keys::y> { auto& operator()(const foo& f){ return f.y; } };
void print(foo f)
{
std::cout << key_to_foo_mem<keys::x>()(f) <<std::endl;
std::cout << key_to_foo_mem<keys::y>()(f) <<std::endl;
}
Your lambda approach has two issues: T
is used but neither provided explicitly nor deduced. The return type is deduced inconsistently from the two cases, but a function can have only one return type. Moreover you use a trailing return of T&
which does surpress the deduction of the return type by the compiler.
You can remove T
and use constexpr if
:
void print(foo f)
{
auto get = [&f]<keys K>(std::integral_constant<keys, K>)
{
if constexpr(K == keys::x) { return f.x; }
else { return f.x; }
};
std::cout << get(std::integral_constant<keys, keys::x>{}) <<std::endl;
std::cout << get(std::integral_constant<keys, keys::y>{}) <<std::endl;
}
> I'm a little confused how the compiler fails to be able to deduce
T&.
When you use trailing return type you are telling the compiler "this is the type" (ie you surpress the deduction) but -> T&
as trailing return type makes no sense. Once you use constexpr if
the compiler can deduce the return type itself (lambdas have implicit auto
return type).
> One way I could work around this is via specializations but I was not able to figure out how to specialize a lambda function for specific template param
Its important to understand that for the above lambda expression it is the operator()
that is templated, not the type of the lambda. How you can use specialization I have shown above (though it is specialization of a class template, not of a member template).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论