Условный оператор возвращает значение из копии своего аргумента - PullRequest
3 голосов
/ 20 мая 2019

Когда два возвращаемых аргумента условного оператора 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*, но только после того, как оно будет скопировано первым.Это выглядит как крайне плохое поведение, но это то, что говорится в спецификации.

Мое текущее решение - просто сделать приведение и ctor 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;
}

1 Ответ

2 голосов
/ 20 мая 2019

Ваша проблема в том, что операнды троичного типа имеют типы std::nullptr_t и struct Fixed. Правило вывода ищет преобразования из одного типа операнда в другой, а также общие базовые классы. Нет возможности сделать вывод char*.

Вы можете поймать ошибку автоматически, предоставив operator std::nullptr_t() = delete;

...