用例是 std::expected 的子类,但以下内容重现了我感兴趣的行为。
#include <type_traits>
template<class T>
struct Foo{
T value;
constexpr explicit operator bool(){return true;}
constexpr Foo() {}
template<class U=T>
constexpr explicit(!std::is_convertible_to<U,T>)
Foo(U&& v) : value(std::forward<U>(v)) {}
};
template <class T>
struct Bar : public Foo<T> {
using Foo<T>::Foo;
}
Bar
继承了Foo
的所有构造函数,包括它们的“显式性”
但是。
在海湾合作委员会(12,13)中
//Foo<bool> foo() {return Foo<int();} //does not compile, as expected
Bar<bool> bar(){ return Bar<int>();}
//compiles!
clang 不会表现出这种行为;即
Bar
的行为与 Foo
相同,并且尝试从返回 Bar<int>
的函数返回 Bar<bool>
无法编译。 神箭
将构造函数(错误,明确地)添加到 Bar 可以解决问题,但是对于 std::expected 中的所有许多复杂构造函数来说,这样做是......不可行的。
我的问题是:
了解标准的人能否阐明这是否是 gcc 中的错误,或者仅仅是一个漏洞?目前我不知道如何措辞错误报告。
最后,有人可以帮我找到解决方法(除了将大部分
<expected>
复制粘贴到我的小子类中)吗?我正在这个项目上使用 gcc-12,(std=c++23,linux,x64),clang 不支持预期的或 c++23 中的其他功能。
template<class T>
using Bar = Foo<T>;
可以工作,但无法自定义 Foo,这就是重点
template <class T>
struct Bar : public Foo<T> {
Bar()=default;
template<class...Args>
Bar(Args&&...args){
Foo<T>& self = *this;
self = { std::forward<Args>(args)...};
}
};
接近,但将要求从构造函数转移到operator=,plus需要T上的默认构造函数,并且不适用于具有多个参数的构造函数。 (std::expected 有一些带有标签:std::in_place_t、std::unexpect_t)
我也许可以解决上述问题,但它离透明包装越来越远。
这个问题涉及这个主题,但早于条件显式(c ++ 20)
这个问题 涉及 Intel 编译器,并在某些版本的标准中提到第 12.9 节,我读到它是说继承的构造函数的所有特征应该是相同的,这令人放心。
具有类似关键字的其他问题不处理条件显式加继承构造函数的交集
我在 gcc bug 跟踪器上发现了 这些 bugs 与我的情况完全匹配。
这回答了我的第一个问题:是的,它是一个错误,它位于 gcc 错误跟踪器上,标准的相关位是 [namespace.udecl] p13
由 using 声明命名的构造函数被视为它们 查找派生类的构造函数时是派生类的构造函数 派生类 ([class.qual]) 或形成一组重载候选者 ([over.match.ctor]、[over.match.copy]、[over.match.list])。
仍然保持开放状态,希望能够想到一些解决方法。
这似乎是一个错误。
template<class From, class To>
concept explicitly_convertible_to = requires {
static_cast<To>(std::declval<From>());
};
template<class From, class To>
concept implicitly_convertible_to = std::is_convertible_v<From, To>;
template<class From, class To>
concept only_explicitly_converible_to =
explicitly_convertible_to<From, To>
&& !implicitly_convertible_to<From, To>;
template<typename T>
struct Foo{
T value;
constexpr explicit operator bool(){return true;}
constexpr Foo() {}
template<implicitly_convertible_to<T> U>
constexpr explicit(false) Foo(U&& v) :
value(std::forward<U>(v))
{
}
template<only_explicitly_converible_to<T> U>
constexpr explicit(true) Foo(U&& v) :
value(std::forward<U>(v))
{
}
};
template<typename T>
struct Bar : public Foo<T>{
using Foo<T>::Foo;
};
当我按照上面的方式重写
Bar
时,Foo
中继承的构造函数具有正确类型的 explicit
。如果我将 static_assert( this is not explicit )
添加到 Bar
构造函数,它会由 Bar<int>
到 Bar<bool>
隐式转换触发,但转换是隐式发生的。
template <class T>
struct Bar : public Foo<T> {
Bar()=default;
template<class...Args>
Bar(Args&&...args):
Foo<T>(std::forward<Args>(args)...)
{}
};
我们可以从这里开始解决您的问题。如前所述,这会导致您丢失隐式/显式标志。我们重复我上面使用的技巧。
我们从
is_explicitly_constructible
开始,即is_constructible
。现在我们尝试写is_implicitly_constructible
:
template<class T, class...Args>
concept implicitly_constructable_from = requires(void(*f)(T), Args&&...args) {
{ f({std::forward<Args>(args)...}) };
};
这会在概念友好的上下文中调用复制列表初始化。
template<class T, class...Args>
concept explicitly_constructable_from = requires(Args&&...args) {
{ T(std::forward<Args>(args)...) };
};
template<class T, class...Args>
concept only_explicitly_constructable_from =
explicitly_constructable_from<T, Args...>
&& !implicitly_constructable_from<T, Args...>;
我们现在可以写我们的
Bar
:
template <class T>
struct Bar : public Foo<T> {
Bar()=default;
template<class...Args>
requires only_explicitly_constructable_from< Foo<T>, Args... >
explicit(true)
Bar(Args&&...args):
Foo<T>(std::forward<Args>(args)...)
{}
template<class...Args>
requires implicitly_constructable_from< Foo<T>, Args... >
explicit(false)
Bar(Args&&...args):
Foo<T>(std::forward<Args>(args)...)
{}
};
我认为这可以满足你的大部分需求。
您将错过的最重要的事情是基于
{}
的(好吧,Bar
)构造参数的构造。