Reci
Reci

Reputation: 4274

How to write function with the matching const modifier on argument and return types?

I want to write a function that extracts a pointer field from a struct. The requirement is that if I pass the struct as a const argument, the returned type should be const. If not, the returned type should not be const.

For instance,

struct S {
    char *p;
};

// approach 1: two overload functions with duplicate body
auto extract(S &input) -> int * {
    return reinterpret_cast<int *>(input.p + 12);
}
auto extract(const S &input) -> const int * {
    return reinterpret_cast<const int *>(input.p + 12);
}

// approach 2: macro
#define macro_extract(input) (reinterpret_cast<int *>(input.p + 12))

Is there any trick in template or latest C++ standard that can write a strongly typed function without duplicating the body?

EDIT: Changed the example a bit to reflect more accurately of the real problem.

Upvotes: 3

Views: 176

Answers (4)

Vasilij
Vasilij

Reputation: 1941

if constexpr and auto as return type solution:

#include <type_traits>

struct S {
    int *p;
};

template<typename T>
auto extract(T &&input) {
    static_assert(std::is_same_v<std::decay_t<decltype(input)>,S>, , "Only struct S is supported");
    if constexpr(!std::is_const_v<std::remove_reference_t<decltype(input)>>) {
        return input.p;
    } else {
        return const_cast<const int*>(input.p);
    }
}

int main () {

    S i;
    using t = decltype(extract(i));
    static_assert(std::is_same_v<t,int*>);

    S const i_c{0};
    using t_c = decltype(extract(i_c));
    static_assert(std::is_same_v<t_c,const int*>);

    return 0;
}

Upvotes: 1

Red.Wave
Red.Wave

Reputation: 4251

PLZ look at the ISO proposal:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4388.html

And the std::experimental::propagate_const spec:

https://en.cppreference.com/w/cpp/experimental/propagate_const

Or one can implement his own version of propagate_const.

have fun, FM

Upvotes: 0

cigien
cigien

Reputation: 60228

Here's a solution with a single function template:

template<typename T, 
         typename = std::enable_if_t<
                      std::is_same_v<
                        std::remove_cv_t<
                          std::remove_reference_t<T>>, S>>>
auto extract(T&& input) 
  -> std::conditional_t<
       std::is_const_v<
         std::remove_reference_t<T>>, int const *, int *> 
{
    return input.p;
}

Here's a demo.

I think it goes without saying that you'd be better off with an overload set. If the function body is large, you can still call the non-const version from the const overload, and add the const there.

Upvotes: 3

Mooing Duck
Mooing Duck

Reputation: 66942

SFINAE should be able to do this. The approximate format is:

template<class T, 
    class allow=std::enable_if_t<std::is_base_of_v<S, T>>>
auto extract(T&& input) -> decltype(input.p) {
    return input.p;
}

Basically using universal forwarding references to make it work for anything: S, S&, const S&, S&&, volatile S&, etc.

Upvotes: -2

Related Questions