Select 第一个有效表达式
Select first valid expression
我正在尝试编写一个函数,允许我从表达式列表中 运行 第一个有效表达式,其中一些表达式可能无效。 例如,这将允许对容器进行统一处理,其中一些可能有 emplace_back
,但有些只有 emplace
.
这是我当前的实现,使用 is_detected
。
template <typename F1, typename... Fs>
class select {
template <typename... Args>
using expr_t = decltype(F1{}(std::declval<Args>()...));
template <typename... Args>
constexpr static bool ok1 = is_detected<expr_t,Args...>::value;
public:
template <typename... Args, std::enable_if_t<ok1<Args...>>* = nullptr>
inline decltype(auto) operator()(Args&&... args) {
return F1{}(std::forward<Args>(args)...);
}
template <typename... Args, std::enable_if_t<!ok1<Args...>>* = nullptr>
inline decltype(auto) operator()(Args&&... args) {
static_assert(sizeof...(Fs),"cannot select valid implementation");
return select<Fs...>{}(std::forward<Args>(args)...);
}
};
#define MEMFCN_SFINAE_WRAP(NAME,EXPR) \
struct NAME { \
template <typename X, typename... Args> \
inline auto operator()(X&& x, Args&&... args) \
-> decltype(EXPR) { return EXPR; } \
};
这是一个用法示例:
#include <iostream>
#include <string>
#include <vector>
#include <map>
MEMFCN_SFINAE_WRAP(emplace_back, x.emplace_back(std::forward<Args>(args)...));
MEMFCN_SFINAE_WRAP(emplace, x.emplace(std::forward<Args>(args)...));
int main(int argc, char* argv[]) {
std::vector<std::pair<std::string,std::string>> v;
std::map<std::string,std::string> m;
select<emplace_back,emplace>{}(v,std::make_pair("hello","world"));
select<emplace_back,emplace>{}(m,std::make_pair("hello","world"));
}
我的问题是是否可以有更优雅的实现或抽象。我特别不喜欢我重复 EXPR
两次,一次是针对 decltype
的 return 类型推导,另一次是在函数体中。
将它与容器上的成员函数一起使用只是一个例子。
一般的想法是使用此构造指定默认代码片段列表以尝试 a priori 未知参数。
另一个例子是有条件地打印 std::cout << x;
,或者打印 x
的类型,或者一些默认消息。
为什么不重用宏的第一个参数??
如:
#define MEMFCN_SFINAE_WRAP(NAME) \
struct NAME { \
template <typename X, typename... Args> \
inline auto operator()(X&& x, Args&&... args) \
-> decltype(x. NAME (std::forward<Args>(args)...)) \
{ return x. NAME (std::forward<Args>(args)...); } \
};
您可以使用宏编程技巧使宏具有可变数量的参数,这适用于 gcc 和 ms 编译器。
#define MEMFCN_SFINAE_WRAP_IMPL(NAME, EXPR, ...) \
struct NAME { \
template <typename X, typename... Args> \
inline auto operator()(X&& x, Args&&... args) \
-> decltype(EXPR) { return EXPR; } \
};
#define MEMFCN_SFINAE_WRAP(NAME, ...) \
MEMFCN_SFINAE_WRAP_IMPL(NAME, ## __VA_ARGS__, x.NAME(std::forward<Args>(args)...))
// call as
MEMFCN_SFINAE_WRAP(emplace_back);
// or
MEMFCN_SFINAE_WRAP(emplace, x.emplace(std::forward<Args>(args)...));
最中肯的评论由 Justin 提供给
look at Boost Hana.
最能表达这种情况的抽象是将一个可能有效的表达式放在 maybe
monad 的上下文中,然后组合结果对象(即定义绑定运算符,因为它在Haskell) 到 运行 第一个 maybe<function>
不是 nothing
(即有效)。这就是 hana::sfinae
所做的,至少是第一部分,放入 maybe
上下文。
在我看来,这是一个更好的抽象,因为它建立在更小的想法之上,可以用更多的方式组合,从而解决更大的 class 问题
我正在尝试编写一个函数,允许我从表达式列表中 运行 第一个有效表达式,其中一些表达式可能无效。 例如,这将允许对容器进行统一处理,其中一些可能有 emplace_back
,但有些只有 emplace
.
这是我当前的实现,使用 is_detected
。
template <typename F1, typename... Fs>
class select {
template <typename... Args>
using expr_t = decltype(F1{}(std::declval<Args>()...));
template <typename... Args>
constexpr static bool ok1 = is_detected<expr_t,Args...>::value;
public:
template <typename... Args, std::enable_if_t<ok1<Args...>>* = nullptr>
inline decltype(auto) operator()(Args&&... args) {
return F1{}(std::forward<Args>(args)...);
}
template <typename... Args, std::enable_if_t<!ok1<Args...>>* = nullptr>
inline decltype(auto) operator()(Args&&... args) {
static_assert(sizeof...(Fs),"cannot select valid implementation");
return select<Fs...>{}(std::forward<Args>(args)...);
}
};
#define MEMFCN_SFINAE_WRAP(NAME,EXPR) \
struct NAME { \
template <typename X, typename... Args> \
inline auto operator()(X&& x, Args&&... args) \
-> decltype(EXPR) { return EXPR; } \
};
这是一个用法示例:
#include <iostream>
#include <string>
#include <vector>
#include <map>
MEMFCN_SFINAE_WRAP(emplace_back, x.emplace_back(std::forward<Args>(args)...));
MEMFCN_SFINAE_WRAP(emplace, x.emplace(std::forward<Args>(args)...));
int main(int argc, char* argv[]) {
std::vector<std::pair<std::string,std::string>> v;
std::map<std::string,std::string> m;
select<emplace_back,emplace>{}(v,std::make_pair("hello","world"));
select<emplace_back,emplace>{}(m,std::make_pair("hello","world"));
}
我的问题是是否可以有更优雅的实现或抽象。我特别不喜欢我重复 EXPR
两次,一次是针对 decltype
的 return 类型推导,另一次是在函数体中。
将它与容器上的成员函数一起使用只是一个例子。
一般的想法是使用此构造指定默认代码片段列表以尝试 a priori 未知参数。
另一个例子是有条件地打印 std::cout << x;
,或者打印 x
的类型,或者一些默认消息。
为什么不重用宏的第一个参数??
如:
#define MEMFCN_SFINAE_WRAP(NAME) \
struct NAME { \
template <typename X, typename... Args> \
inline auto operator()(X&& x, Args&&... args) \
-> decltype(x. NAME (std::forward<Args>(args)...)) \
{ return x. NAME (std::forward<Args>(args)...); } \
};
您可以使用宏编程技巧使宏具有可变数量的参数,这适用于 gcc 和 ms 编译器。
#define MEMFCN_SFINAE_WRAP_IMPL(NAME, EXPR, ...) \
struct NAME { \
template <typename X, typename... Args> \
inline auto operator()(X&& x, Args&&... args) \
-> decltype(EXPR) { return EXPR; } \
};
#define MEMFCN_SFINAE_WRAP(NAME, ...) \
MEMFCN_SFINAE_WRAP_IMPL(NAME, ## __VA_ARGS__, x.NAME(std::forward<Args>(args)...))
// call as
MEMFCN_SFINAE_WRAP(emplace_back);
// or
MEMFCN_SFINAE_WRAP(emplace, x.emplace(std::forward<Args>(args)...));
最中肯的评论由 Justin 提供给
look at Boost Hana.
最能表达这种情况的抽象是将一个可能有效的表达式放在 maybe
monad 的上下文中,然后组合结果对象(即定义绑定运算符,因为它在Haskell) 到 运行 第一个 maybe<function>
不是 nothing
(即有效)。这就是 hana::sfinae
所做的,至少是第一部分,放入 maybe
上下文。
在我看来,这是一个更好的抽象,因为它建立在更小的想法之上,可以用更多的方式组合,从而解决更大的 class 问题