条件运算符从其参数的副本返回值
Posted
技术标签:
【中文标题】条件运算符从其参数的副本返回值【英文标题】:Conditional operator returns value from a copy of its argument 【发布时间】:2019-05-20 17:42:00 【问题描述】:当条件运算符c?x:y
的两个返回参数的类型不同时,会在应用强制转换之前进行复制。可以在保持简单易用性的同时防止这种情况吗?
我有这个(因问题而删减):
struct Fixed
char data[10];
Fixed(char *s) strcpy(data, s);
operator char*() return this->data;
;
但它对条件运算符和 nullptr 的行为非常糟糕:
Fixed f =...; // just here to show the type of f, don't read too much into this
...
bool condition = ...;
char *s = condition ? nullptr : f;
制作了 f 的副本,并且 s 现在指向堆栈上的一个值,该值将很快消失。这一切都是因为nullptr
的类型是std::nullptr_t
。 f
将通过演员表传递给char*
,但只有在它首先被复制之后。这似乎是非常糟糕的行为,但这是规范所说的。
我目前的解决方法是只制作演员和演员explicit
,但这有点破坏可用性。还有其他解决方案吗?
这里有一些示例代码可供使用(忽略质量,因为我正在大量使用它以了解 gcc 和 LLVM 如何以不同方式处理此问题):
#include <cstdio>
#include <iostream>
#include <array>
#include <cstring>
using namespace std;
struct A : public array<char,4>
A() cerr<<"def\n";
A(const A &o) cerr<<"copy\n"; (*this)=o;
A(const char *s) cerr<<"ctor\n";assign(s); // explicit fixes
void assign(const char*s) cerr<<"assign\n";memset(this->begin(), 0, 4); strncpy(this->begin(), s, 4);
operator char*() cerr<<"cast\n";return this->begin();
//operator void*() cerr<<"void\n";return this->begin();
//operator std::nullptr_t() cerr<<"void\n";return (std::nullptr_t)this->begin();
;
volatile A *faraway = new A();
char* plain(A &v) cerr<<"in pl\n";
return faraway == nullptr ? nullptr : v;
char* cast1(A &v) cerr<<"in c1\n";
return faraway == nullptr ? (char*)nullptr : v;
char* cast2(A &v) cerr<<"in c2\n";
return faraway == nullptr ? nullptr : (char*)v;
int main()
A *a = new A; a->assign("asd");
char *x = a->data();
cerr << "\nplain\n";
char *yp = plain(*a);
cerr << "\nc1\n";
char *y1 = cast1(*a);
cerr << "\nc2\n";
char *y2 = cast2(*a);
cerr << "\n---\n";
cerr << (void*)a << "\n" << (void*)(a->data()) << "\n" << (void*)x << "\n---\n";
cerr << (void*)yp << "\n" << (void*)y1 << "\n" << (void*)y2 << "\n";
return 0;
【问题讨论】:
这里发生了很多奇怪的事情cond?nullptr:(char*)f
工作正常。 cond?(char*)nullptr:f
在 LLVM 中工作正常,但 GCC 拒绝编译(我明确指定了 Fixed(char*)
ctor)。
@JohnKugelman 是的,基本上。我只是在那里展示f
的类型。该结构是从哈希中检索的,因此我尝试在哈希中返回结构的char*
,而不是返回堆栈上结构副本的char*
。
使用somefunc(A &v)
然后&v
不能为nullptr。请展示一个更好的例子或更好的设计。
@Ripi2:令人惊讶的是,这不是真的。你可以做somefunc(*(A*)nullptr)
,只要不做左值到右值的转换是合法的。
@BenVoigt 给我一个惊喜,是的。无论如何,我认为 OP 想要使用 char *x = somefunc(someinstance)
,其中 someinstance
不是指针,而是类实例。
【参考方案1】:
您的问题是三元的操作数有std::nullptr_t
和struct Fixed
类型。推理规则查找从一种操作数类型到另一种的转换,以及常见的基类。没有机会推断char*
。
您可以通过提供operator std::nullptr_t() = delete
自动发现错误;
【讨论】:
但它似乎确实推断出char*
。它甚至调用了正确的operator char*
方法。正是这种奇怪的需要首先创建一个临时对象(以及 GCC 需要 Fixed(char*)
ctor 但实际上并未使用它的奇怪行为)。
@JasonN:它不推断char*
,三元计算结果为struct Fixed
临时对象。然后在char*
变量的初始化期间调用operator char*()
。您可以通过在两者之间引入auto
-typed 变量来证明这一点,例如auto t = condition ? nullptr : f; cout << "Between ternary and char* initialization"; char* s = t;
我认为三元,必须从两个分支返回相同的类型,在这种情况下,它需要在三元之前进行扣除(即,我不能返回 A
,除非如果可以从nullptr
转换为A
)。 ...我刚刚使用auto
进行了测试,并强制编译器错误以查看类型。三元返回一个A
,这也意味着可能存在从nullptr
到A
的隐式转换?我不明白这怎么可能?
为了增加更多的丑陋,如果比较为真,nullptr
将转换为 A 段错误,没有编译器警告。为什么甚至可以将 nullptr_t 转换为任何非指针类型?再玩一点,我实际上可以让编译器承认它可以从nullptr_t
转换为A
和/viseversa/。丑陋,但我想不会比其他 C++ 丑陋差多少。耸肩
你认为这是一个 GCC 错误,需要 A(char*)
编译,不像 LLVM,并且从不使用它?我应该举报吗?以上是关于条件运算符从其参数的副本返回值的主要内容,如果未能解决你的问题,请参考以下文章