AILien
AILien

Reputation: 842

Is there a way to avoid implicit conversion to void*?

I'm using an API that accepts void* in certain functions. I frequently accidentally pass the wrong pointer type to the function, and of course it compiles fine, but doesn't work at runtime.

Is there a way to disable implicit conversion to void* for pointers to a certain class?

Upvotes: 42

Views: 4292

Answers (4)

Davislor
Davislor

Reputation: 15144

If the argument is meant to be opaque to the client, you might expose it as a handle type that does not implicitly convert, e.g.

#include <cstdint>

using api_handle = std::uintptr_t;

inline api_handle make_api_handle(const char* p)
{
  return (api_handle)(const void*)p;
}

inline api_handle make_api_handle(const int* p)
{
  return (api_handle)(const void*)p;
}

The two-stage conversion is because the language standard technically only says that round-trip conversions between any object pointer and void* are safe, and that round-trip conversions between void* and uintptr_t or intptr_t are safe. Within your library, you can do the conversion the opposite way to retrieve the original pointer.

That somewhat-contrived example lets you convert specific types of pointers to handles explicitly, but pointers do not convert to handles implicitly. (Although, now, integral values will implicitly convert to handles, and give you undefined behavior.) In the real world, the helper functions should be optimized out.

If this kind of approach works for your API, another solution would be to wrap your void* in a minimal struct and pass those around. Modern compilers should be able to pass them around in registers, just like pointers. You might also add a field storing type or other information. Another option might be to keep a vector of valid objects and pass around indices to that, like file handles in Unix.

For constructors, you can use the explicit keyword to disable all implicit conversion on the arguments.

Upvotes: 4

Ted Lyngmo
Ted Lyngmo

Reputation: 117298

Is there any way to disable implicit conversion to void* for pointers to a certain class?

No, you can't prevent the implicit conversion, but you could wrap the API function(s) in proxy functions that checks types at compile time and approve/disapprove them there.

Example:

#include <iostream>
#include <string>
#include <type_traits>

void api(void* p) { // your original API
    std::cout << "void* " << p << '\n';
}

template<class T>
void api_wrapper(T* p) { // your wrapper
    // let constness fail in the original api instead of in the static_assert:
    using type = std::remove_const_t<T>*;

    static_assert(
        // add your approved types here
        std::is_convertible_v<type, std::ostream*> ||
        std::is_convertible_v<type, std::string*>,
        "Not an approved type"
    );
    api(p);
}

int main() {
    std::string foo;
    api_wrapper(&std::cout);
    api_wrapper(&foo);
    //api_wrapper(&std::cin); // compile time error "Not an approved type"
}

If the set of pointer types that you would like to disapprove is very small, then instead of listing all the approved types in the static_assert, just list the disapproved types and adjust the boolean logic:

    static_assert(
        // add your disapproved types here
        not std::is_convertible_v<type, std::ostream*> &&
        not std::is_convertible_v<type, std::string*>,
        "Not an approved type"
    );

Upvotes: 40

Aykhan Hagverdili
Aykhan Hagverdili

Reputation: 29975

You can add a deleted overload for the API function:

// API function
void api(void* p) {
    // ... 
}

// Special case for nullptr
inline void api(std::nullptr_t) {
    api((void*)nullptr);
}

// Everything else is disabled
template <class ...T>
void api(T&&... t) = delete;

int main() {
    int i = 0;
    void* p = &i;
    api(p); // Compiles
    // api(&i); // Doesn't compile
}

Upvotes: 37

Nathan Pierson
Nathan Pierson

Reputation: 5565

From the standard, expr.conv.ptr:

A prvalue of type “pointer to cv T”, where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The pointer value (basic.compound) is unchanged by this conversion.

You cannot disallow this conversion.

Upvotes: 21

Related Questions