对于给定的模板和给定的模板参数集,
...
在程序中最多只能定义一次显式特化(根据[basic.def.odr]),和
...
类模板的显式(完整)特化的定义不能放在标题中(否则每个包含此标题的翻译单元中都有一个定义,因此整个程序中将有多个定义).
此外,作为另一个证据,[basic.def.odr]/12(下面的块引用)中列出的实体不包含类模板的完全特化.而是包含" 未指定某些模板参数的模板特化 ".
可以有多个类类型的定义,枚举类型,带外部链接的内联函数([dcl.inline]),带外部链接的内联变量([dcl.inline]),类模板,非静态函数模板, concept([temp.concept]),类模板的静态数据成员,类模板的成员函数,或未指定某些模板参数的模板特化([temp.spec],[temp.class.spec])在程序中,每个定义出现在不同的翻译单元中,并且定义满足以下要求.
但是,如果我将定义放在源文件中并将其声明保留在标题中,例如,
// "a.h"
template <typename T>
struct S {};
template <>
struct S<int>; // declaration
// "a.cpp"
#include "a.h"
template <>
struct S<int> {}; // definition
// "main.cpp"
#include "a.h"
int main()
{
S<int> s;
}
Run Code Online (Sandbox Code Playgroud)
然后发生错误(由gcc测试),因为它S<int>是一个不完整的类型.
总之,我应该在哪里放置类模板的显式特化的定义?
有人可以帮助我理解为什么我的编译器不能/不推断这个?(使用g ++ 7.3)
不起作用:
#include <array>
std::array<std::array<double,2>,2> f() {
return {{0,0},{0,0}};
}
Run Code Online (Sandbox Code Playgroud)
工作良好:
#include <array>
std::array<std::array<double,2>,2> f() {
return {std::array<double,2>{0,0},{0,0}};
}
Run Code Online (Sandbox Code Playgroud)
奇怪的是,这也失败了:
#include <array>
std::array<std::array<double,2>,2> f() {
return std::array<std::array<double,2>,2>{{0,0},{0,0}};
}
Run Code Online (Sandbox Code Playgroud)
@ 1201ProgramAlarm指出添加另一组花括号有效:
#include <array>
std::array<std::array<double,2>,2> f() {
return {{{0,0},{0,0}}};
}
Run Code Online (Sandbox Code Playgroud)
它使用聚合初始化,因为std::array没有用于brace-init-list的构造函数.那没关系,但那么为什么/如何运作?
std::array<double,2> x{1,2};
Run Code Online (Sandbox Code Playgroud)
为什么它处理这种情况但不处理嵌套的情况?
#include <type_traits>
template<typename T>
struct remove_cvref
{
using type = std::remove_cv_t<
std::remove_reference_t<T>>;
};
template<typename T>
using remove_cvref_t =
typename remove_cvref<T>::type;
template<typename T>
constexpr bool isCc = std::is_copy_constructible_v<
remove_cvref_t<T>>;
class A final
{
public:
A() = default;
template<typename T, bool = isCc<T>> // error
A(T&&) {}
};
A f()
{
A a;
return a;
}
int main()
{}
Run Code Online (Sandbox Code Playgroud)
错误消息:
error : constexpr variable 'isCc<const A &>' must be initialized by a constant expression
1>main.cpp(14): note: in instantiation of variable template specialization …Run Code Online (Sandbox Code Playgroud) 考虑以下程序:
#include<stdexcept>
#include<iostream>
int main() {
try {
throw std::range_error(nullptr);
} catch(const std::range_error&) {
std::cout << "Caught!\n";
}
}
Run Code Online (Sandbox Code Playgroud)
带有 libstdc++ 的 GCC 和 Clang 调用std::terminate并使用消息中止程序
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct null not valid
Run Code Online (Sandbox Code Playgroud)
使用 libc++ 段错误构建异常的 Clang。
见神箭。
编译器的行为是否符合标准?标准[diagnostics.range.error] (C++17 N4659)的相关部分确实说std::range_error有一个const char*构造函数重载,应该优先于const std::string&重载。该部分也没有说明构造函数的任何先决条件,而只说明了后置条件
后置条件:
strcmp(what(), what_arg) == 0。
如果what_arg是空指针,则此后置条件总是具有未定义的行为,那么这是否意味着我的程序也具有未定义的行为并且两个编译器的行为都一致?如果不是,应该如何阅读标准中这种不可能的后置条件?
再想一想,我认为这对我的程序来说一定意味着未定义的行为,因为如果不是,那么也将允许(有效)指针不指向以空字符结尾的字符串,这显然没有意义。
因此,假设这是真的,我想将问题更多地集中在标准如何暗示这种未定义的行为上。是否因为调用也具有未定义的行为或先决条件被简单地遗忘了后置条件的不可能性?
受到这个问题的启发。
给出以下示例:
#include <iostream>
struct A{
A() = default;
A(A const&){}
};
struct B:A{};
struct C{
explicit operator B(){
return B{};
}
};
int main(){
C c;
A a(c); // #1
}
Run Code Online (Sandbox Code Playgroud)
GCC和Clang两个报告c都不能转换为const A&. 但是,标准中有一条相关规则说,explicit operator B()在这种情况下应该是候选函数。即:
over.match.copy#1.2
当初始化一个临时绑定到构造函数的第一个参数时,该参数的类型是“对可能有 cv 限定的 T 的引用”,并且在直接初始化类型对象的上下文中使用单个参数调用构造函数时“cv2 T”,也考虑了显式转换函数。那些没有隐藏在 S 中并且产生其 cv 非限定版本与 T 类型相同的类型或其派生类的类型是候选函数。
而且,在现行标准中,相关规则的含义仍然相同:
over.match.copy#1.2
当初始化表达式的类型是类类型“cv S”时,考虑转换函数。非显式转换函数的允许类型是T 和从 T 派生的任何类。初始化临时对象 ([class.mem]) 以绑定到构造函数的第一个参数时,其中该参数的类型为“对 cv2 T 的引用”,并且在直接初始化的上下文中使用单个参数调用构造函数对于“cv3 T”类型的对象,显式转换函数的允许类型是相同的 …
我使用g ++(7.1)和clang ++(xcode 9.0)编译了以下程序-std=c++11 -Wall并获得结果:
克++
0x10052c050
0x10052c040
0x10052c040
Run Code Online (Sandbox Code Playgroud)
铛++
0x108b74024
0x108b74018
0x108b74018
Run Code Online (Sandbox Code Playgroud)
这意味着extern int a[];和static int a[3];声明相同的实体并具有相同的链接(内部链接).
//a.cpp
#include <stdio.h>
int a[3];
void f()
{
printf("%p\n", (void*)a);
};
//b.cpp
extern void f();
static int a[3];
void g()
{
printf("%p\n", (void*)a);
extern int a[];
printf("%p\n", (void*)a);
}
int main(int argc, char* argv[])
{
f();
g();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
但是,在C++ 11 [3.9/6]中:
...数组对象的声明类型可能是一个未知大小的数组,因此在翻译单元的某一点不完整,稍后会完成; 这两个点的数组类型("T的未知边界数组"和"NT数组")是不同的类型....
int a[3];并且int a[];是不同的类型,
在C++ 11 [3.5/6]中:
如果与具有相同名称和类型关联实体的可见声明,忽略了最内层的命名空间范围之内声明的实体, …
这个问题的后续问题这一个.
考虑以下程序:
#include <cmath>
// meaningless, only for language-lawyer purpose
void abs(void*) {}
int main(){
abs(nullptr);
}
Run Code Online (Sandbox Code Playgroud)
该程序是否导致未定义的行为?
标准中的相关部分是[extern.names]/4:
来自使用外部链接声明的C标准库的每个函数签名保留给实现,以用作具有extern"C"和extern"C++"链接的函数签名,或者作为全局命名空间中的命名空间范围的名称.
我不确定是否允许超载.
我想将一个可变参数类型列表"实现"到相关值的initializer_list中.例如,有std::tuple几个std::integral_constant<T, x>得到一个std::initializer_list<T>{...}.一般情况下,我想得到一些复杂类型的initializer_list,比如std::string.
但是下面这个简单的例子让我在Clang编译时崩溃了(虽然它适用于GCC,至少在Coliru上),所以我怀疑UB(或Clang中的bug):
template <class... Ts>
std::initializer_list<const std::string> materialize()
{
return {
std::to_string(Ts::value)...
};
}
void print_out()
{
for (const auto & x : materialize<std::true_type, std::false_type>()) {
std::cout << x << "\n";
}
}
Run Code Online (Sandbox Code Playgroud)
那么,这样的代码合法吗?在C++ 11/14/17中?
我正在尝试提供一个包装器std::invoke来完成推导函数类型的工作,即使函数已重载。
(我昨天为可变参数和方法指针版本问了一个相关问题)。
当函数有一个参数时,此代码 (C++17) 在正常重载条件下按预期工作:
#include <functional>
template <typename ReturnType, typename ... Args>
using FunctionType = ReturnType (*)(Args...);
template <typename S, typename T>
auto Invoke (FunctionType<S, T> func, T arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&> func, T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, const T&> func, const T & arg)
{
return std::invoke(func, arg);
}
template <typename S, …Run Code Online (Sandbox Code Playgroud) c++ templates function-pointers template-meta-programming type-deduction
请原谅我的无知,在我看来,这std::array意味着您的常规数组的 STL 替代品。但是因为数组大小必须作为模板参数传递,它阻止我们std::array使用仅在运行时已知的大小进行创建。
std::array<char,3> nums {1,2,3}; // Works.
constexpr size_t size = 3;
std::array<char,size> nums {1,2,3}; // Works.
const buf_size = GetSize();
std::array<char, buf_size> nums; // Doesn't work.
Run Code Online (Sandbox Code Playgroud)
我认为 C++ 中数组的一个非常重要的用例是基于运行时输入创建一个固定大小的数据结构(比如为读取文件分配缓冲区)。
我为此使用的解决方法是:
// Create a array pointer for on-the-spot usecases like reading from a file.
char *data = new char[size];
...
delete[] data;
Run Code Online (Sandbox Code Playgroud)
或者:
// Use unique_ptr as a class member and I don't want to manage the memory myself.
std::unique_ptr<char[]> myarr_ = std::unique_ptr<char[]>(new char[size]);
Run Code Online (Sandbox Code Playgroud)
如果我不关心固定大小,我知道我可以使用 …