【问题标题】:Continue with the continuation monad tuple. Whats wrong?继续延续单子元组。怎么了?
【发布时间】:2014-10-21 10:20:44
【问题描述】:

tuple continuation monad 之后,假设我定义了一个函子 std_tuple 以从 monad-tuple 的范畴转到 std::tuple

auto std_tuple = [](auto... args)
{
    return [=](auto f){ return f(std::make_tuple(args...)); };
};

现在我们可以在期望 std::tuple 的上下文中使用 monad-tuples:

template<typename... ARGS>
void f( const std::tuple<ARGS...>& t ){}

int main()
{
    tuple(1,2,3)(std_tuple)(f);
}

到目前为止一切顺利。除了这不编译。 Clang 3.4.1 抱怨:

注意:候选模板被忽略:无法推断模板参数“$auto-1-0”

std_tuple 函子内的f(t) 调用上。

这是否正确,这些模板参数不是可推导出的吗?如果是肯定的,为什么?

【问题讨论】:

  • 闭包类型的apply操作符是template&lt;class T&gt; decltype(auto) operator() (T)这样的函数模板,它不能只为id-expressionf推导类型。您可以将 f 包装在 lambda 中,[](auto const&amp; t){ return f(t); }
  • 一个完美的转发 lambda-wrapper 来传递一个重载集对于 C++14 的多态 lambdas 非常有用。不幸的是,这是一些没有宏的样板:#define LAMBDA_WRAP(FUN) ([](auto&amp;&amp;... args){ return FUN(std::forward&lt;decltype(args)&gt;(args)...); })
  • @dyp 为什么 () 在 lambda 周围?
  • @Yakk @dyp 两个连续的左方括号标记(例如,下标表达式中的 lambda-introducer)被解析为引入属性;见 dcl.attr.grammar/6。此外,delete 关键字后面的空 lambda-introducer 必须用括号括起来,否则将被解析为 delete[];见 expr.delete/1。
  • @ecatmur 下标可能会破坏其他工作代码——array[LAMBDA_WRAP(f)]——这是对宏的有效使用(将重载集对象传递给下标运算符,例如用于切片目的)。另一个,没那么多(有效)。 (你不应该直接调用LAMBDA_WRAP(f),你应该只在将f本身作为参数传递给某事物时使用它)

标签: c++ templates tuples monads c++14


【解决方案1】:

重现您的问题的简单案例:

void f(int) {}
void f(double) {}

template<class T> void call_with_3( T t ) { t(3); }

int main() {
  call_with_3( f );
}

在这里我们可以看到,在我们将其传递给call_with_3 时,无法确定调用哪个f。现在,您似乎没有多个重载(您只有一个 f!),但是...

template 不是实例。 template 函数是函数工厂,而不是函数。

那里没有可以传递的对象或值。

当您将函数名作为参数传递时,重载解析就会启动。如果目标类型已知(作为函数引用或指针),它将用于对函数名进行重载解析。

在这种情况下,您将函数名称传递给templateauto 参数),因此无法进行重载决策,因此无法找到特定值,因此您会收到错误消息。

您可以创建一个对象,其效果是对具有给定函数名称的调用参数进行重载解析。我称它们为重载集对象。

static struct f_overload_set_t {
  template<class... Args>
  auto operator()(Args&&... args) const {
    return f(std::forward<Args>(args)...);
  }
} f_overload_set;

在 C++11 中,您需要在 const 之后添加一个 -&gt;decltype( f( std::declval&lt;Args&gt;()... ) )

现在f_overload_set(blah) 将在被调用时(几乎)执行f(blah) 时发生的事情,但f_overload_set 是一个实际对象。这样你就可以传递它了。

生成此类重载集的宏相对容易编写。他们也可以使用 lambda,因为如果你仔细想想,上面很像一个无状态的 lambda。

基于无状态 lambda 的宏重载集生成器的优点在于它可以在使用点创建。来自@dyp 上面的评论:

#define OVERLOAD_SET( FUNC )\
  ([](auto&&... args){\
    return FUNC(std::forward<decltype(args)>(args)...);\
  })

(注意:FUNC 周围没有括号,因为这会阻止 ADL)。括号括起其他所有内容,否则如果在下标操作 (operator[]) 中使用,它将被解析为 [[ 开始一个属性,以及其他点(感谢@ecatmur))

这使您的代码:

template<typename... ARGS>
void f( const std::tuple<ARGS...>& t ){}

int main() {
  tuple(1,2,3)(std_tuple)(OVERLOAD_SET(f));
}

【讨论】:

  • 所以底层operator()的签名是template&lt;typename T&gt; auto operator()( T ),我正在传递一个模板。当然,f 参数在这种情况下是不可推导的,这是有道理的。我只是忘记了通用 lambda 实际上是什么。非常感谢。
猜你喜欢
  • 2010-10-14
  • 2011-10-30
  • 2011-02-09
  • 1970-01-01
  • 2014-02-17
  • 2014-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多