在模板评估期间允许隐式类型转换
Allow implicit type conversion during template evaluation
我正在为 C++ 类型编写一个包装器 class,它允许我在构造、访问、修改和销毁包装对象时进行检测。为了使这对原始代码透明,我将隐式转换函数包含回基础类型,但是当包装对象直接传递给模板时这会失败,因为不评估隐式转换。下面是一些演示此问题的代码:
#include <utility>
// simplified wrapper class
template <typename T>
class wrap {
T t;
public:
wrap() : t() {}
wrap(const T& _t) : t(_t) {}
wrap(T&& _t) : t(std::move(_t)) {}
constexpr operator T&() { return t; }
constexpr operator const T&() const { return t; }
};
// an example templated function
template <typename T>
bool is_same(const T& t1, const T& t2) { return t1 == t2;}
// second invocation fails due to template substitution failure
bool problem() {
wrap<int> w(5);
return is_same(static_cast<int>(w), 5) && is_same<>(w, 5);
}
我可以通过在每个模板调用站点(如第一次调用所示)对包装变量执行 static_cast
来手动解决此问题,但这不能很好地扩展,因为我正在使用大型代码库。 Similar questions 建议将每个模板函数内联为一个 friend
函数,但这也需要识别和复制每个模板,这不会缩放。
对于如何 (1) 使用模板函数解决此转换问题,或 (2) 以其他方式在没有此问题的情况下在源代码级别检测变量,我将不胜感激。
如果您将“内部”人视为指针,它可以使用指针包装函数。
这不是一个完整的解决方案,但对您来说应该是一个很好的起点(例如,您需要仔细编写复制和移动 ctors)。
你可以玩这个代码in here。
免责声明:我从 Andrei Alexandrescu 的 this 演讲中得到了这个想法。
#include <iostream>
using namespace std;
template <typename T>
class WrapperPtr
{
T * ptr;
public:
WrapperPtr(const WrapperPtr&){
// ???
}
WrapperPtr(WrapperPtr&&) {
// ???
}
WrapperPtr(const T & other)
: ptr(new T(other)) {}
WrapperPtr(T * other)
: ptr(other) {}
~WrapperPtr()
{
// ????
delete ptr;
}
T * operator -> () { return ptr; }
T & operator * () { return *ptr; }
const T & operator * () const { return *ptr; }
bool operator == (T other) const { other == *ptr; }
operator T () { return *ptr; }
};
// an example templated function
template <typename T>
bool my_is_same(const T& t1, const T& t2) { return t1 == t2;}
bool problem() {
WrapperPtr<int> w(5);
return my_is_same(static_cast<int>(w), 5) && my_is_same(*w, 5);
}
这个例子的错误在于is_same
。它声明它需要两个相同类型的参数,这是它不需要的要求,并且没有要求该类型具有它确实需要的 ==
。
诚然,通常会发现 C++ 对模板函数的约束不佳,因为否则很难且冗长。作者采取了一条实用的捷径。也就是说,修复is_same
接口的方法不是吗?
// C++17 version. Close to std::equal_to<>::operator().
template <typename T, typename U>
constexpr auto is_same(T&& t, U&& u)
noexcept(noexcept(std::forward<T>(t) == std::forward<U>(u)))
-> decltype(std::forward<T>(t) == std::forward<U>(u))
{
return std::forward<T>(t) == std::forward<U>(u);
}
经过更正 is_same
,代码正常工作。
还有其他可以想象的例子,可能需要两个参数具有相同的类型。例如,如果 return 类型取决于参数类型并且 return 值可以来自:
template <typename T>
T& choose(bool choose_left, T& left, T& right) {
return choose_left ? left : right;
}
这种情况要少得多。但它实际上可能需要考虑来决定是使用底层类型还是包装类型。如果您在包装器类型中具有这种增强行为,并且有条件地使用包装值或底层值,是否应该包装底层值以继续获得增强行为,或者我们是否放弃增强?即使你能让它默默地选择这两种行为中的一种,你愿意吗?
但是,您仍然可以使获取值比说 static_cast<T>(...)
更容易,例如通过提供访问器:
// given wrap<int> w and int i
is_same(w.value(), 5);
choose_left(true, w.value(), i);
我还有一些重要的意见:
wrap() : t() {}
这要求 T 是默认可构造的。 = default
做正确的事。
wrap(const T& _t) : t(_t) {}
wrap(T&& _t) : t(std::move(_t)) {}
这些并不明确。 T
可以隐式转换为 wrap<T>
,反之亦然。这在 C++ 中效果不佳。例如,true ? w : i
格式不正确。这导致 std::equality_comparable_with<int, wrap<int>>
为假,这将是 is_same
的合理要求。包装类型可能应该显式构造,并且可以隐式转换为基础类型。
constexpr operator T&() { return t; }
constexpr operator const T&() const { return t; }
这些不是引用限定的,因此它们 return 左值引用,即使包装器是右值。这似乎是不明智的。
最后,构造和转换只考虑确切的类型T
。但是任何使用 T
的地方,它都可能从其他类型隐式转换而来。在 C++ 中不允许进行两次转换。因此,对于包装器类型,需要做出决定,这通常意味着允许从 T
可构造的任何东西构造。
我正在为 C++ 类型编写一个包装器 class,它允许我在构造、访问、修改和销毁包装对象时进行检测。为了使这对原始代码透明,我将隐式转换函数包含回基础类型,但是当包装对象直接传递给模板时这会失败,因为不评估隐式转换。下面是一些演示此问题的代码:
#include <utility>
// simplified wrapper class
template <typename T>
class wrap {
T t;
public:
wrap() : t() {}
wrap(const T& _t) : t(_t) {}
wrap(T&& _t) : t(std::move(_t)) {}
constexpr operator T&() { return t; }
constexpr operator const T&() const { return t; }
};
// an example templated function
template <typename T>
bool is_same(const T& t1, const T& t2) { return t1 == t2;}
// second invocation fails due to template substitution failure
bool problem() {
wrap<int> w(5);
return is_same(static_cast<int>(w), 5) && is_same<>(w, 5);
}
我可以通过在每个模板调用站点(如第一次调用所示)对包装变量执行 static_cast
来手动解决此问题,但这不能很好地扩展,因为我正在使用大型代码库。 Similar questions 建议将每个模板函数内联为一个 friend
函数,但这也需要识别和复制每个模板,这不会缩放。
对于如何 (1) 使用模板函数解决此转换问题,或 (2) 以其他方式在没有此问题的情况下在源代码级别检测变量,我将不胜感激。
如果您将“内部”人视为指针,它可以使用指针包装函数。
这不是一个完整的解决方案,但对您来说应该是一个很好的起点(例如,您需要仔细编写复制和移动 ctors)。
你可以玩这个代码in here。
免责声明:我从 Andrei Alexandrescu 的 this 演讲中得到了这个想法。
#include <iostream>
using namespace std;
template <typename T>
class WrapperPtr
{
T * ptr;
public:
WrapperPtr(const WrapperPtr&){
// ???
}
WrapperPtr(WrapperPtr&&) {
// ???
}
WrapperPtr(const T & other)
: ptr(new T(other)) {}
WrapperPtr(T * other)
: ptr(other) {}
~WrapperPtr()
{
// ????
delete ptr;
}
T * operator -> () { return ptr; }
T & operator * () { return *ptr; }
const T & operator * () const { return *ptr; }
bool operator == (T other) const { other == *ptr; }
operator T () { return *ptr; }
};
// an example templated function
template <typename T>
bool my_is_same(const T& t1, const T& t2) { return t1 == t2;}
bool problem() {
WrapperPtr<int> w(5);
return my_is_same(static_cast<int>(w), 5) && my_is_same(*w, 5);
}
这个例子的错误在于is_same
。它声明它需要两个相同类型的参数,这是它不需要的要求,并且没有要求该类型具有它确实需要的 ==
。
诚然,通常会发现 C++ 对模板函数的约束不佳,因为否则很难且冗长。作者采取了一条实用的捷径。也就是说,修复is_same
接口的方法不是吗?
// C++17 version. Close to std::equal_to<>::operator().
template <typename T, typename U>
constexpr auto is_same(T&& t, U&& u)
noexcept(noexcept(std::forward<T>(t) == std::forward<U>(u)))
-> decltype(std::forward<T>(t) == std::forward<U>(u))
{
return std::forward<T>(t) == std::forward<U>(u);
}
经过更正 is_same
,代码正常工作。
还有其他可以想象的例子,可能需要两个参数具有相同的类型。例如,如果 return 类型取决于参数类型并且 return 值可以来自:
template <typename T>
T& choose(bool choose_left, T& left, T& right) {
return choose_left ? left : right;
}
这种情况要少得多。但它实际上可能需要考虑来决定是使用底层类型还是包装类型。如果您在包装器类型中具有这种增强行为,并且有条件地使用包装值或底层值,是否应该包装底层值以继续获得增强行为,或者我们是否放弃增强?即使你能让它默默地选择这两种行为中的一种,你愿意吗?
但是,您仍然可以使获取值比说 static_cast<T>(...)
更容易,例如通过提供访问器:
// given wrap<int> w and int i
is_same(w.value(), 5);
choose_left(true, w.value(), i);
我还有一些重要的意见:
wrap() : t() {}
这要求 T 是默认可构造的。 = default
做正确的事。
wrap(const T& _t) : t(_t) {}
wrap(T&& _t) : t(std::move(_t)) {}
这些并不明确。 T
可以隐式转换为 wrap<T>
,反之亦然。这在 C++ 中效果不佳。例如,true ? w : i
格式不正确。这导致 std::equality_comparable_with<int, wrap<int>>
为假,这将是 is_same
的合理要求。包装类型可能应该显式构造,并且可以隐式转换为基础类型。
constexpr operator T&() { return t; }
constexpr operator const T&() const { return t; }
这些不是引用限定的,因此它们 return 左值引用,即使包装器是右值。这似乎是不明智的。
最后,构造和转换只考虑确切的类型T
。但是任何使用 T
的地方,它都可能从其他类型隐式转换而来。在 C++ 中不允许进行两次转换。因此,对于包装器类型,需要做出决定,这通常意味着允许从 T
可构造的任何东西构造。