directory_entry 的输出与具有变体的 class 的重载 ostream operator<< 冲突
Output for directory_entry conflicts with overloaded ostream operator<< for class with variant
我有一个项目,我从 directory_iterator 打印 std::filesystem::directory_entry
。另一方面,我有一个完全独立的 class 重载 std::ostream& operator<<
,它有一个模板构造函数,它初始化一个 std::variant
成员。
#include <variant>
#include <iostream>
#include <filesystem>
typedef std::variant<long, std::string> VarType;
class Var {
VarType _value;
public:
template<typename T>
Var(T value) : _value{value} {
}
};
std::ostream& operator<< (std::ostream& stream, const Var&) {
return stream;
}
int main() {
std::cout << std::filesystem::directory_entry() << "\n";//tigger compling error
return 0;
}
编译失败:
main.cpp: In instantiation of ‘Var::Var(T) [with T =
std::filesystem::__cxx11::directory_entry]’: main.cpp:25:49:
required from here main.cpp:11:30: error: no matching function for
call to ‘std::variant<long int, double,
std::__cxx11::basic_string<char, std::char_traits<char>,
std::allocator<char> > >::variant(<brace-enclosed initializer list>)’
Var(T value) : _value{value} {
... several pages of output ...
它似乎试图将 directory_entry
包装成 Var
,然后再发送到 cout
,但我不确定。
能否请您解释一下实际发生的情况以及代码错误的原因?
我测试了一下。对于这个问题,无论我在 variant
中输入什么,它似乎都会起作用,即使是单个变体也是错误的。这个
#include <variant>
#include <iostream>
#include <filesystem>
typedef std::variant<long, std::string> VarType;
class Var {
VarType _value;
public:
template<typename T>
Var(T value) : _value{value} {
}
};
std::ostream& operator<< (std::ostream& stream, const VarType&) {
return stream;
}
int main() {
std::cout << std::filesystem::directory_entry() << "\n";
return 0;
}
工作正常。如果我将 _value
初始化移动到 c-tor 主体编译失败并出现相同的逻辑错误,但对于 operator=
,至少它是一致的。显然它适用于非模板化的 c-tor。
如果我将 ostream& operator<<
的实现移动到一个单独的单元中并将其定义为 Var
的友元,则编译通过(这是一种合适的解决方法,但不应该 operator<<
可以访问 class 的私有权限)。但是,如果我只是分开,不交朋友,那就失败了。
main.cpp:
#include "var.hpp"
#include <iostream>
#include <filesystem>
int main() {
std::cout << std::filesystem::directory_entry() << "\n";
std::cout << Var(1l) << "\n";
return 0;
}
var.hpp:
#include <variant>
#include <ostream>
typedef std::variant<long, std::string> VarType;
class Var {
VarType _value;
public:
template<typename T>
Var(T value) : _value{value} {
}
friend std::ostream& operator<< (std::ostream& stream, const Var&); //works
};
//std::ostream& operator<< (std::ostream& stream, const Var&); //instead above does not works
var.cpp:
#include "var.hpp"
std::ostream& operator<< (std::ostream& stream, const Var&) {
return stream;
}
这让我完全迷路了。假设它尝试在 <<
上调用 Var
c-tor,这里应该没有区别。
为什么这样的改变很重要?
我用 g++8.4 构建(g++ -std=c++17 main.cpp var.cpp -lstdc++fs
,我也试过 clang7.0 也有类似的结果)。
在 Mansoor 的提示下,我想我找到了。
C-tor
这段代码很危险。
class Var {
public:
template<typename T>
Var(T value) {
}
};
避免这种情况。
编译器将尝试在 Var
可见且可能合适的任何隐式转换中替换此类 c-tor。
将 c-tor 标记为 explicit
是限制这种随意替换的最直接的方法。 SFINAE 和 enable_if
可以通过其他方式来限制替换。
在我的案例中,错误的替换正是打破复杂性的原因,因为在旧的 complies directory_entry
中没有 ostream<<
的直接定义。编译器寻找转换器并找到合适的Var
。可以实例化但不能遵守。后者很好,因为如果可以的话,错误是无法追踪的。
有一个补丁 https://gcc.gnu.org/legacy-ml/gcc-patches/2018-06/msg01084.html 大约在 2018 年(或者可能很快就修复了)引入了显式 ostream << directory_entry
。
在此之前 directory_entry
可以通过隐式 c-tor 转换为 path
template<typename _Source,
typename _Require = _Path<_Source>>
path(_Source const& __source, format = auto_format)
这就是为什么 ostream << directory_entry
在没有明确定义 ostream<<
的情况下起作用的原因。
朋友
Friend 实际上限定了转换器的可见性。这也有效
class Var {
VarType _value;
public:
template<typename T>
Var(T value) : _value{value} {
}
friend std::ostream& operator<< (std::ostream& stream, const Var&) {
return stream;
}
};
但是,如果 std::ostream&
被声明为友元并且定义(或其他声明)对 ostream << directory_entry
可见,当它再次打破复杂化时,因为其他声明对于野性替换是可见的。这就解释了为什么分成几个单元并使用 friend 创建了一个解决方法。
SFINAE
SFINAE 不检查函数体。它仅适用于声明。 _value{value}
是正文。调用 SFINAE c-tor 应该像
template< class T, typename = std::enable_if_t<std::disjunction_v<std::is_same<T, long>, std::is_same<T, std::string>>>>
Var(T value) : _value{value} {
}
如何处理 std::variant
依赖 c-tor 的绝妙想法可以在这里找到:
.
我有一个项目,我从 directory_iterator 打印 std::filesystem::directory_entry
。另一方面,我有一个完全独立的 class 重载 std::ostream& operator<<
,它有一个模板构造函数,它初始化一个 std::variant
成员。
#include <variant>
#include <iostream>
#include <filesystem>
typedef std::variant<long, std::string> VarType;
class Var {
VarType _value;
public:
template<typename T>
Var(T value) : _value{value} {
}
};
std::ostream& operator<< (std::ostream& stream, const Var&) {
return stream;
}
int main() {
std::cout << std::filesystem::directory_entry() << "\n";//tigger compling error
return 0;
}
编译失败:
main.cpp: In instantiation of ‘Var::Var(T) [with T =
std::filesystem::__cxx11::directory_entry]’: main.cpp:25:49:
required from here main.cpp:11:30: error: no matching function for
call to ‘std::variant<long int, double,
std::__cxx11::basic_string<char, std::char_traits<char>,
std::allocator<char> > >::variant(<brace-enclosed initializer list>)’
Var(T value) : _value{value} {
... several pages of output ...
它似乎试图将 directory_entry
包装成 Var
,然后再发送到 cout
,但我不确定。
能否请您解释一下实际发生的情况以及代码错误的原因?
我测试了一下。对于这个问题,无论我在 variant
中输入什么,它似乎都会起作用,即使是单个变体也是错误的。这个
#include <variant>
#include <iostream>
#include <filesystem>
typedef std::variant<long, std::string> VarType;
class Var {
VarType _value;
public:
template<typename T>
Var(T value) : _value{value} {
}
};
std::ostream& operator<< (std::ostream& stream, const VarType&) {
return stream;
}
int main() {
std::cout << std::filesystem::directory_entry() << "\n";
return 0;
}
工作正常。如果我将 _value
初始化移动到 c-tor 主体编译失败并出现相同的逻辑错误,但对于 operator=
,至少它是一致的。显然它适用于非模板化的 c-tor。
如果我将 ostream& operator<<
的实现移动到一个单独的单元中并将其定义为 Var
的友元,则编译通过(这是一种合适的解决方法,但不应该 operator<<
可以访问 class 的私有权限)。但是,如果我只是分开,不交朋友,那就失败了。
main.cpp:
#include "var.hpp"
#include <iostream>
#include <filesystem>
int main() {
std::cout << std::filesystem::directory_entry() << "\n";
std::cout << Var(1l) << "\n";
return 0;
}
var.hpp:
#include <variant>
#include <ostream>
typedef std::variant<long, std::string> VarType;
class Var {
VarType _value;
public:
template<typename T>
Var(T value) : _value{value} {
}
friend std::ostream& operator<< (std::ostream& stream, const Var&); //works
};
//std::ostream& operator<< (std::ostream& stream, const Var&); //instead above does not works
var.cpp:
#include "var.hpp"
std::ostream& operator<< (std::ostream& stream, const Var&) {
return stream;
}
这让我完全迷路了。假设它尝试在 <<
上调用 Var
c-tor,这里应该没有区别。
为什么这样的改变很重要?
我用 g++8.4 构建(g++ -std=c++17 main.cpp var.cpp -lstdc++fs
,我也试过 clang7.0 也有类似的结果)。
在 Mansoor 的提示下,我想我找到了。
C-tor
这段代码很危险。
class Var {
public:
template<typename T>
Var(T value) {
}
};
避免这种情况。
编译器将尝试在 Var
可见且可能合适的任何隐式转换中替换此类 c-tor。
将 c-tor 标记为 explicit
是限制这种随意替换的最直接的方法。 SFINAE 和 enable_if
可以通过其他方式来限制替换。
在我的案例中,错误的替换正是打破复杂性的原因,因为在旧的 complies directory_entry
中没有 ostream<<
的直接定义。编译器寻找转换器并找到合适的Var
。可以实例化但不能遵守。后者很好,因为如果可以的话,错误是无法追踪的。
有一个补丁 https://gcc.gnu.org/legacy-ml/gcc-patches/2018-06/msg01084.html 大约在 2018 年(或者可能很快就修复了)引入了显式 ostream << directory_entry
。
在此之前 directory_entry
可以通过隐式 c-tor 转换为 path
template<typename _Source,
typename _Require = _Path<_Source>>
path(_Source const& __source, format = auto_format)
这就是为什么 ostream << directory_entry
在没有明确定义 ostream<<
的情况下起作用的原因。
朋友
Friend 实际上限定了转换器的可见性。这也有效
class Var {
VarType _value;
public:
template<typename T>
Var(T value) : _value{value} {
}
friend std::ostream& operator<< (std::ostream& stream, const Var&) {
return stream;
}
};
但是,如果 std::ostream&
被声明为友元并且定义(或其他声明)对 ostream << directory_entry
可见,当它再次打破复杂化时,因为其他声明对于野性替换是可见的。这就解释了为什么分成几个单元并使用 friend 创建了一个解决方法。
SFINAE
SFINAE 不检查函数体。它仅适用于声明。 _value{value}
是正文。调用 SFINAE c-tor 应该像
template< class T, typename = std::enable_if_t<std::disjunction_v<std::is_same<T, long>, std::is_same<T, std::string>>>>
Var(T value) : _value{value} {
}
如何处理 std::variant
依赖 c-tor 的绝妙想法可以在这里找到: