假设我正在lib命名空间中编写一些通用算法,该算法调用自定义点my_func。
第一次尝试是为my_func使用ADL,其中一个用户希望为他的类型专门化my_func,这是std类型的别名。当然,在他的命名空间中定义它不起作用,因为ADL不能用于别名。标准不允许在std命名空间中定义它。剩下的唯一选项似乎是在算法的命名空间lib中定义的。但是,如果最终用户在包括自定义头之前包含算法头,则这也不起作用。
#include <iostream>
#include <array>
// my_algorithm.hpp
namespace lib{
template<typename T>
void my_algorithm(const T& t){
my_func(t);
}
} // namespace lib
// user1.hpp
namespace user1{
struct Foo1{
// this is working as expected (ADL)
friend void my_func(const Foo1&){
std::cout << "called user1's customisation\n";
}
};
} // namespace user1
// user2.hpp
namespace user2{
using Foo2 = std::array<int,1>;
// this won't work because Foo2 is actually in std namespace
void my_func(const Foo2&){
std::cout << "called user2's customisation\n";
}
} // namespace user2
/* surely this isn't allowed
namespace std{
void my_func(const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
} //namespace std
*/
// another attempt to costomize in the algorithm's namespace
// this won't work because my_func isn't seen before my_algorithm
namespace lib{
void my_func(const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
}
// main.cpp
// #include "algorithm.hpp"
// #include "user1.hpp"
// #include "user2.hpp"
int main(){
lib::my_algorithm(user1::Foo1{});
lib::my_algorithm(user2::Foo2{});
}第二次尝试是在my_func中使用niebloid,它与ADL有相同的问题。
第三次尝试是使用tag_invoke,它应该有与ADL相同的问题,即,
用户名称空间中的type
std的别名,std中的std不是lib名称空间中的
#include <iostream>
#include <array>
// tag_invoke.hpp overly simplified version
namespace lib_ti{
inline namespace tag_invoke_impl{
inline constexpr struct tag_invoke_fn{
template<typename CP, typename... Args>
decltype(auto) operator()(CP cp, Args&&... args) const{
return tag_invoke(cp, static_cast<Args&&>(args)...);
}
} tag_invoke{};
} // namespace tag_invoke_impl
} // namespace lib_to
// my_algorithm.hpp
// #include "tag_invoke.hpp"
namespace lib{
inline constexpr struct my_func_fn {
template <typename T>
void operator()(const T& t) const{
lib_ti::tag_invoke(*this, t);
}
} my_func{};
template<typename T>
void my_algorithm(const T& t){
my_func(t);
}
} // namespace lib
// user1.hpp
namespace user1{
struct Foo1{
// this is working as expected (ADL)
friend void tag_invoke(lib::my_func_fn, const Foo1&){
std::cout << "called user1's customisation\n";
}
};
} // namespace user1
// user2.hpp
namespace user2{
using Foo2 = std::array<int,1>;
// this won't work because Foo2 is actually in std namespace
void tag_invoke(lib::my_func_fn, const Foo2&){
std::cout << "called user2's customisation\n";
}
} // namespace user2
/* surely this isn't allowed
namespace std{
void tag_invoke(lib::my_func_fn, const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
} //namespace std
*/
// another attempt to customise in the algorithm's namespace
// In ADL case, this does not work. But in this case, it seems to work. why?
namespace lib{
void tag_invoke(lib::my_func_fn, const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
}
// main.cpp
int main(){
lib::my_algorithm(user1::Foo1{});
lib::my_algorithm(user2::Foo2{});
}为什么没有第一个问题(原始ADL)?
第四种尝试是使用模板专门化,这似乎像预期的那样正常工作。
#include <iostream>
#include <array>
// my_algorithm.hpp
namespace lib{
template<typename T, typename = void>
struct my_func_impl{
//void static apply(const T&) = delete;
};
inline constexpr struct my_func_fn {
template <typename T>
void operator()(const T& t) const{
using impl = my_func_impl<std::decay_t<T>>;
impl::apply(t);
}
} my_func{};
template<typename T>
void my_algorithm(const T& t){
my_func(t);
}
} // namespace lib
// user1.hpp
namespace user1{
struct Foo1{};
} // namespace user1
namespace lib{
template<>
struct my_func_impl<user1::Foo1>{
void static apply(const user1::Foo1&){
std::cout << "called user1's customisation\n";
}
};
} //namespace lib
// user2.hpp
namespace user2{
using Foo2 = std::array<int,1>;
} // namespace user2
namespace lib{
template<>
struct my_func_impl<user2::Foo2>{
void static apply(const user2::Foo2&){
std::cout << "called user2's customisation\n";
}
};
}
// main.cpp
int main(){
lib::my_algorithm(user1::Foo1{});
lib::my_algorithm(user2::Foo2{});
}编写通用算法和自定义点并允许客户端为性病类型的别名定制的最佳方法是什么?
发布于 2020-12-16 20:59:10
用户之一希望为他的类型专门化
my_func,后者是std类型的别名。
这就是原罪,它给你带来了所有的痛苦。C++中的类型别名只是别名;它们不是新类型。您有一个使用自定义点的通用算法,如下所示
// stringify_pair is my generic algorithm; operator<< is my customization point
template<class T>
std::string stringify_pair(K key, V value) {
std::ostringstream oss;
oss << key << ':' << value;
return std::move(oss).str();
}您的用户希望用标准类型调用这个通用算法,如
std::string mykey = "abc";
std::optional<int> myvalue = 42;
std::cout << stringify_pair(mykey, myvalue);这不起作用,因为std::optional<int>没有提供operator<<。它不可能工作,因为您的用户不拥有std::optional<int>类型,因此无法向其添加操作。(从身体上讲,他们当然可以尝试;但从哲学的角度来看,这是行不通的,这就是为什么你每次(身体上)接近时都会遇到路障。)
用户使代码工作的最简单方法是“合法拥有”类型定义,而不是依赖其他人的类型。
struct OptionalInt {
std::optional<int> data_;
OptionalInt(int x) : data_(x) {}
friend std::ostream& operator<<(std::ostream&, const OptionalInt&);
};
OptionalInt myvalue = 42; // no problem now您会问为什么tag_invoke不存在与原始ADL相同的问题。我相信答案是,当您调用lib::my_func(t) (它调用lib_ti::tag_invoke(*this, t),它对tag_invoke(lib::my_func, t)执行ADL调用)时,它使用的参数列表包括您的t (这并不重要)和第一个lib::my_func_fn类型的参数(这意味着lib是与此调用相关的命名空间)。这就是为什么它会发现您将tag_invoke重载放入namespace lib中的原因。
在原始ADL情况下,namespace lib不是调用my_func(t)的关联命名空间。您放置到namespace lib中的namespace lib重载是找不到的,因为它不是由ADL找到的(不是在关联的命名空间中),也不是通过常规的非限定查找(因为waves模糊地进行了两阶段查找)。
:编写通用算法和自定义点以及允许客户端为性病类型定制别名的最佳方法是什么?
不要。类型的“接口”--它支持什么操作,允许您对它做什么--由类型的作者控制。如果您不是该类型的作者,不要向它添加操作;相反,创建您自己的类型(可能是通过继承,最好是通过组合),并给它您想要的任何操作。
在最坏的情况下,您将在程序的不同部分使用两个不同的用户,其中一个用户正在执行
using IntSet = std::set<int>;
template<> struct std::hash<IntSet> {
size_t operator()(const IntSet& s) const { return s.size(); }
};另一个在做
using IntSet = std::set<int>;
template<> struct std::hash<IntSet> {
size_t operator()(const IntSet& s, size_t h = 0) const {
for (int i : s) h += std::hash<int>()(i);
return h;
}
};然后他们都尝试使用std::unordered_set<IntSet>,然后在运行时将std::unordered_set<IntSet>从一个对象文件传递到另一个对象文件,并且他们同意std::hash<std::set<int>>的名称,但在其含义上存在分歧。只是一大罐虫子。别打开它。
https://stackoverflow.com/questions/65286657
复制相似问题