C ++ 17检查有效表达式

时间:2018-10-26 14:02:32

标签: c++ c++17

根据我在thisthis上的代码,我编写了此版本:

#include <iostream>
#include <boost/preprocessor.hpp>
#include <boost/callable_traits/is_invocable.hpp>

#define IS_VALID_EXPANDER_BEGIN(count)                    \
    [](BOOST_PP_REPEAT(count, IS_VALID_EXPANDER_MIDDLE, \
        _)) constexpr->decltype IS_VALID_EXPANDER_END

#define IS_VALID_EXPANDER_MIDDLE(z, idx, _) BOOST_PP_COMMA_IF(idx) auto _##idx

#define IS_VALID_EXPANDER_END(...) \
    (__VA_ARGS__){})

#define IS_VALID(...)                              \
    is_valid<__VA_ARGS__>(IS_VALID_EXPANDER_BEGIN( \
        BOOST_PP_VARIADIC_SIZE(__VA_ARGS__))

template <typename... Ts, typename TF>
static constexpr auto is_valid(TF)
{
    return boost::callable_traits::is_invocable<std::decay_t<TF>(Ts...), Ts...>{};
}

struct Test {};

int main()
{
    std::cout << IS_VALID(std::ostream&, double)(_0 << _1) << std::endl;
    std::cout << IS_VALID(std::ostream&, Test)(_0 << _1) << std::endl;
}

但是,结果是:

1
1

我不知道为什么。

2 个答案:

答案 0 :(得分:1)

您的lambda按值接受参数,这不允许您测试std::ostream& << T。将宏更改为:

#define IS_VALID_EXPANDER_BEGIN(count)                    \
    [](BOOST_PP_REPEAT(count, IS_VALID_EXPANDER_MIDDLE, \
        &&_)) constexpr->decltype IS_VALID_EXPANDER_END

另外,您对is_invocable的使用是错误的-应该是

template <typename... Ts, typename TF>
static constexpr auto is_valid(TF)
{
    return boost::callable_traits::is_invocable<std::decay_t<TF>, Ts...>{};
}

(无论如何,您都应该使用std::is_invocable,它在C ++ 17中可用。)

live example on wandbox.org

答案 1 :(得分:1)

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

template<class F>
constexpr auto invokeable( F&& f ) {
  return [](auto&&...args) {
    return std::is_invocable< F&&, decltype(args)... >{};
  };
}

现在我们可以用更少的宏魔术了:

std::cout << invokeable([](auto& lhs, auto&& v) RETURNS( lhs << v ))( std::cout, 0.0 ) << std::endl;
std::cout << invokeable([](auto& lhs, auto&& v) RETURNS( lhs << v ))( std::cout, Test{} ) << std::endl;

Live example

如果_0 << _1构建了一个SFINAE友好的可调用对象(看起来如此),您甚至可以:

std::cout << invokeable(_0 << _1)( std::cout, Test{} ) << std::endl;

invokeable是一个功能对象适配器。它将功能对象变成测试器,以查看传递给它的参数是否合法。

它确实要求您实际上具有正确类型的参数。您可以通过以下方法解决此问题:

template<class T>
struct tag_t { using type=T; };
template<class T>
constexpr tag_t<T> tag{};

template<class X>
struct untag { using type=X; };
template<class X>
using untag_t = typename untag<X>::type;
template<class T>
struct untag<tag_t<T>> { using type=T; };
template<class T>
struct untag<tag_t<T>&&> { using type=T; };
template<class T>
struct untag<tag_t<T>&> { using type=T; };
template<class T>
struct untag<tag_t<T>const &> { using type=T; };

然后修改:

template<class F>
constexpr auto invokeable( F&& f ) {
  return [](auto&&...args) {
    return std::is_invocable< F&&, untag_t<decltype(args)>... >{};
  };
}

允许传递tag<std::ostream&>而不是类型std::ostream&的左值来测试所有std::ostream&是否可以在该插槽中工作。

Live example

此处使用的唯一宏是RETURNS,这使对SFINAE友好的lambda变得更容易。无论如何,有很多建议在中添加与RETURNS等效的内容。