放置/分段语法是否可能出现懒惰"operator or"重载?

Is a lazy "operator or" overload possible with placement/piecewise syntax?

本文关键字:operator or 重载 分段 语法 是否 放置      更新时间:2023-10-16

所以我们都去过那里。我有一个不错的对象,我有一个构造函数,如果正确创建,该对象可以解析 true,但它也有其他状态,所以我可以愉快地编写:

if (const auto& str = std::ofstream("/tmp/myfile"))
{
str << "Oh that was easyn";
}

但我真正想做的是尝试一些替代方案:

if (const std::stream& str = (std::ofstream("/tmp/myfile") || std::ofstream("/temp/myfile") || std::ofstream("./myfile")) )
{
str << "Oh that was not so easyn";
}

显然不是!我可能打开了 3 个文件,但将它们转换为"真"布尔值,当然无法转换回 std::stream。如果我为流 rvals 重载operator ||,那么它会打开所有 3 个文件,因为忘记了惰性计算!

我知道,std::stream 可能是一个糟糕的例子,但 20 页后我将完成对我的选项字符串处理器的解释,所以让我们坚持使用 std::stream。

我所期望的可能是这样的:

if (const std::stream str = (std::piecewise_construct(std::ofstream,"/tmp/myfile") || std::piecewise_construct(std::ofstream,"/temp/myfile") || std::piecewise_construct(std::ofstream,"./myfile")) )
{
str << "Oh that maybe makes sense?n";
}

为此,看起来我可能需要 2 个重载operator ||,一个需要两个未解决piecewise_construct并解析左侧一个以开始使用,另一个在左侧获取 rval 对象,并且仅在"失败"时评估右侧。像这样:

template<class PC2>
std::stream operator ||(std::stream&& str, const PC2& pc2)
{
if (str)
return std::move(str);
return pc2.construct();
}
template<class PC1, class PC2>
std::stream operator ||(const PC1& pc1, const PC2& pc2)
{
return operator||(pc1.construct(), pc2);
}
Where PCx here is the piecewise construct.

但我可以直接看到std::p eicewise_construct只是一个标签。它本身没有任何功能。此外,我正在努力寻找任何可以使用模板 FINAE 限制来使其工作的方法:创建两个无法移动的不相关类型没有多大意义。 C++中流传着许多类似的"技巧",例如 std::function 或 lambda,它们看起来可以做类似的工作?

现在,我希望在 C++11 中有一个解决方案,在与他的怪物战斗后看起来不像弗兰肯斯坦,但如果我想要的功能只是一个版本,请不要感到受限!

同时,我有 3 个基本相同的参数值解析器和存储的副本,因为客户无法就版本之间应该调用的选项达成一致。

我想我追求的是某种可自定义的模板,而不是硬连线到特定类的东西,在这种情况下是 std::stream,甚至 stream 也有许多构造函数。一个足够常见的句柄模式是基本上想要执行h = (open(O_APP) || open(O_CREATE) || open(O_TRUNC))的代码{除了C,而不是对象}。

一种方法是某种模板化类,它保存 std::bind 并知道如何构造,进而从帮助程序函数构建。也许该对象的类型将允许我也想要的类型保护。

还有一点...在示例中,我使用 std::istream 只有 1 个参数,但当然 istream 的构造函数可以接受许多参数并具有许多替代构造函数,并且我的内部用例至少有 3 个选项,将来可能还有更多选项,所以它真的需要支持我们都喜欢的任意参数列表模板的东西。

最简单的实现可能是编写一个函数,该函数采用一系列文件名并返回第一个成功打开的文件名:

std::ofstream first_file_of(std::initializer_list<std::string> names) {
for (auto&& name : names) {
std::ofstream os(name);
if (os) return os;
}
return std::ofstream();
}

如:

first_file_of({"/tmp/myfile", "/temp/myfile", "./myfile"})

但是如果你真的想对运算符发疯,你需要一些对象,它有一些重载的二进制运算符来执行这种条件。

使用operator|的一个这样的实现将是:

struct or_else_file {
std::string name;
or_else_file(std::string name) : name(name) { }
friend std::ofstream operator|(std::ofstream&& os, or_else_file oef) {
if (os) {
return std::move(os);
} else {
return std::ofstream(oef.name);
}
}
};

如:

std::ofstream("/tmp/myfile") | or_else_file("/temp/myfile") | or_else_file("./myfile")

这更复杂,更冗长,但可以说根据您的观点阅读更好。

试试这个:

template<class Tuple, std::size_t...Is>
struct try_make_t {
Tuple inputs;
template<std::size_t I, class T>
bool try_make( std::optional<T>& out ) {
out.emplace( std::get<I>(inputs) );
return (bool)*out;
}
template<class T>
operator T()&&{
std::optional<T> retval;
( try_make<Is>( retval ) || ... );
if (!retval)
return {};
return std::move(*retval);
}
};
template<class...Ins, std::size_t...Is>
try_make_t< std::tuple<Ins&&...>, Is... > try_make_helper( std::index_sequence<Is...>, Ins&&...ins ) {
return {std::tuple<Ins&&...>( std::forward<Ins>(ins)... )};
}
template<class...Ins>
auto try_make_from( Ins&&...ins ) {
return try_make_helper( std::make_index_sequence<sizeof...(ins)>{}, std::forward<Ins>(ins)... );
}
int main() {
if (std::ofstream&& str = try_make_from("/tmp/myfile", "/temp/myfile", "./myfile") )
{
str << "Oh that was easyn";
}
}

用于更高级的结构

template<class F>
struct factory {
F f;
template<class T,
std::enable_if_t< std::is_convertible_v< decltype(std::declval<F&>()()), T>, bool> =true
>
operator T()&& { return f(); }
template<class T,
std::enable_if_t< std::is_convertible_v< decltype(std::declval<F&>()()), T>, bool> =true
>
operator T()&& { return f(); }
template<class T,
std::enable_if_t< !std::is_convertible_v< decltype(std::declval<F&>()()), T>, bool> =true
>
operator T()&& { return std::make_from_tuple<T>(f()); }
};

其中factory{ []{ return something; } }将从 lambda 的返回值或从 lambda 返回的元组创建一个任意对象。

if(
std::ofstream&& file = try_make_from(
factory{[]{return "/tmp/myfile";}},
factory{[]{return std::make_tuple("/temp/myfile");}},
"./myfile"
)
)