akim
akim

Reputation: 8759

Cast from 'void *' to 'const std::string *&' must have all intermediate pointers const qualified

I have this piece of code which uses iostreams' xalloc and pword to store flags of various types as pointers. Since pword exposes a void*&, I have a simple wrapper to expose the stored type via an old C cast. But since its version 5, clang issues a warning that I don't understand well. I don't see how to avoid it. The simple test case looks as follows:

#include <iostream>
#include <string>

using namespace std::literals;

void*& pword()
{
  static void* ptr;
  return ptr;
}

const std::string*& sword()
{
  return (const std::string*&) pword();
}

int main()
{
  const auto s1 = "foo"s;
  const auto s2 = "bar"s;
  sword() = &s1;
  std::cerr << *sword() << '\n';
  sword() = &s2;
  std::cerr << *sword() << '\n';
}

The warnings I get (which seem to forget the reference on void*):

clang++-mp-devel -std=c++17 -Wcast-qual foo.cc && ./a.out 
foo.cc:14:32: warning: cast from 'void *' to 'const std::string *&' (aka 'const basic_string<char,
      char_traits<char>, allocator<char> > *&') must have all intermediate pointers const qualified
      to be safe [-Wcast-qual]
  return (const std::string*&) pword();
                               ^
1 warning generated.
foo
bar

Also, I fail to see how to use C++ casts instead of the C almighty cast. Obvious attempts:

const std::string*& sword2()
{
  return static_cast<const std::string*&>(pword());
}

end with errors I don't understand. The error seems to give a hint that the compiler might feel obliged to insert a copy somewhere, breaking the chain of references, but I don't see where and why.

foo.cc:19:10: error: non-const lvalue reference to type 'const std::string *' (aka 'const
      basic_string<char, char_traits<char>, allocator<char> > *') cannot bind to a value of
      unrelated type 'void *'
  return static_cast<const std::string*&>(pword());
         ^                                ~~~~~~~

Upvotes: 1

Views: 694

Answers (1)

Davis Herring
Davis Herring

Reputation: 39818

This is equivalent to the famous fact that you can’t convert char** to const char**—because it would allow

const char cc='a';
const char *cp=&cc;
char *p;
const char **pp=&p;  // error in question
*pp=cp;  // i.e., p=&cc;
*p='z';  // oops

In this case, by describing a void* as a const std::string*, you can store pointers to constant strings there (like you do) and then use a static_cast on the void* to obtain a non-const pointer to the string.

The answer is to use const_cast to add const and then (as HolyBlackCat said) reinterpret_cast (and then be careful not to try modifying the strings!):

const std::string*& sword()
{
  return reinterpret_cast<const std::string*&>(const_cast<const void*&>(pword()));
}

Aside from const-correctness, I do not remember definitively at the moment whether storing a T* into a void* variable via such a reinterpret_cast is defined behavior; it is, however, something one expects to work in common implementations.

Upvotes: 1

Related Questions