【问题标题】:Why clang is not able to instantiate nested variadic template with defaulted integer_sequence?为什么 clang 不能用默认的 integer_sequence 实例化嵌套的可变参数模板?
【发布时间】:2017-04-18 10:37:35
【问题描述】:

考虑一个例子:

#include <utility>

template <class... Ts>
struct pack { 
   static constexpr std::size_t size = sizeof...(Ts);
};

template <class P, class = std::make_index_sequence<P::size>>
struct ipack;

template <class... Ts, std::size_t... Is>
struct ipack<pack<Ts...>, std::index_sequence<Is...>> { 
   static constexpr std::size_t size = sizeof...(Ts);
};

template <class IP, class = std::make_index_sequence<IP::size>>
struct vpack;

template <class... Ts, std::size_t... Is>
struct vpack<ipack<pack<Ts...>>, std::index_sequence<Is...>> { 
   static constexpr std::size_t size = sizeof...(Ts);
};

int main() {
    vpack<ipack<pack<int, int, int>>> vp;
    static_cast<void>(vp);
}

clang reports 有问题:

prog.cc:29:39: error: implicit instantiation of undefined template 'vpack<ipack<pack<int, int, int>, std::__1::integer_sequence<unsigned long, 0, 1, 2> >, std::__1::integer_sequence<unsigned long, 0, 1, 2>
vpack<ipack<pack<int, int, int>>> vp;
                                  ^

gcc does not share 铿锵情愫。哪个编译器是对的?上面的代码在某种程度上是不是格式错误?

【问题讨论】:

  • 我无法使用 Apple 的 clang Apple LLVM version 8.1.0 (clang-802.0.41) 重现您的编译错误——不确定这对应于哪个正常版本的 clang。编译后的二进制文件似乎也可以正常工作;例如,您的示例中的vp.size 等于 3。
  • @jwimberley 所以你说这可能是 wandbox 的 clang 版本的问题?
  • 我复制并粘贴了 WandBox 使用的编译命令,clang++ prog.cc -Wall -Wextra -O2 -march=native -std=c++14 -pedantic(一些设置从您的原始帖子更改,但编译器错误相同)。这再次使 Apple Clang 没有错误。
  • 这同样是 WandBox 的 libc++ 或它使用的任何库的问题吗?可能模板与其库中定义的std::integer_sequence 不匹配...
  • 它与 stdlib 无关(@jwimberley),它与 Clang 版本有关; 3.8.1 compiles cleanly,3.9+ 没有。所以,这要么是回归,要么是错误修复——问题是哪个。如果是后者并且需要解决方法,here 很简单。

标签: c++ templates c++14


【解决方案1】:

我无法使用godbolt 重现您的错误。 Clang 和 gcc 编译就好了。

但是,在使用编译器时,我发现 msvc 不喜欢您的代码,因为 ipack 中的默认参数。但是,如果您直接提供参数,它会起作用:

template <class...Ts, std::size_t... Is>
struct vpack<ipack<pack<Ts...>,std::index_sequence<Is...>>, std::index_sequence<Is...>> { 
   static constexpr std::size_t size = sizeof...(Ts);
};

此更改也修复了您的 clang 错误。 (我不知道如何在 wandbox 中获取链接...)

编辑:

msvc 指出了另一个错误,我在上面省略了。 vpack 必须是可构造的。但是,因为您刚刚声明了它 (struct vpack;),所以没有可用的默认构造函数。您可以通过定义它来解决这个问题,使用:struct vpack {};。这也解决了clang问题。 (即使没有上述内容。)

编辑 2:

思考为什么你需要使用struct vpack {}; 我在你的代码中发现了另一个缺陷。它可以简化为:

#include <utility>

template <class... Ts>
struct pack { 
   static constexpr std::size_t size = sizeof...(Ts);
};

template <class P, class = std::make_index_sequence<P::size>>
struct ipack {};

template <class... Ts, std::size_t... Is>
struct ipack<pack<Ts...>, std::index_sequence<Is...>> { 
   static constexpr std::size_t size = sizeof...(Ts);
};

template <class IP, class = std::make_index_sequence<IP::size>>
struct vpack {};

int main() {
    vpack<ipack<pack<int, int, int>>> vp;
    static_cast<void>(vp);
}

原因是,您的模板特化 vpack 没有被实例化,因为您使用的是主模板的默认参数。 (vpack&lt;ipack&lt;pack&lt;int, int, int&gt;&gt;&gt; 只提供一个参数,而不是专业化所需的两个。)

您甚至可以删除 ipack 的模板特化,如果不是因为您在 vpack 主数据库中隐式使用它。 (IP::size指的是ipack的特化。)

(我写了一个版本:https://godbolt.org/g/6Gbsvd

因此,msvc 和 wandboxes clang 拒绝编译您的代码是正确的。我不确定为什么它在 gcc 和 godbolts clang 下工作。它可能与处理默认参数的方式有关...

有趣的是,您可以通过在 vpack 主中定义 size 来查看差异:

#include <utility>

template <class... Ts>
struct pack { 
   static constexpr std::size_t size = sizeof...(Ts);
};

template <class P, class = std::make_index_sequence<P::size>>
struct ipack;

template <class... Ts, std::size_t... Is>
struct ipack<pack<Ts...>, std::index_sequence<Is...>> { 
   static constexpr std::size_t size = sizeof...(Ts);
};

template <class IP, class = std::make_index_sequence<IP::size>>
struct vpack { static constexpr std::size_t size = 0; };

template <class... Ts, std::size_t... Is>
struct vpack<ipack<pack<Ts...>,std::make_index_sequence<sizeof...(Ts)>>, std::index_sequence<Is...>> { 
   static constexpr std::size_t size = sizeof...(Ts);
};

int main() {
    vpack<ipack<pack<int, int, int>>> vp;

    return decltype(vp)::size;
}

gcc 和 clang 返回 3,但 msvc 返回 0

【讨论】:

  • Codebolt 的 clang 确实没有提供编译错误。我想知道这里的魔杖盒问题的原因是什么......
  • 我发现了问题。您正在尝试从没有构造函数的结构构造对象。 (见编辑。)这当然是做不到的。但是,为什么它在 gcc 和 Godbolt 中起作用是我无法理解的。这可能是一些特殊的编译器功能,仅适用于空类...
  • 实际上,如果编译器会选择适当的特化——第 20 行中的那个对象是可构造的,所以我想说这不是根本原因,而是它的影响......
  • 嗯...必须在标准中查找确切的措辞。但我想,一般情况必须是可构造的,因为编译器可能需要构造它,即使实际上它从来没有。
  • @W.F.我做了第二次编辑,争论为什么你的专业化没有像你想象的那样实例化;)
猜你喜欢
  • 1970-01-01
  • 2021-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-10
  • 2020-03-30
  • 2015-07-29
  • 1970-01-01
相关资源
最近更新 更多