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 问题