1

Basicly, I need a static_cast function wrapper to use as a predicate (for conversion), since static_cast directly is not usable in this way. Lambda is not preferred this time. My implemention:

template<typename T> 
struct static_cast_forward{
  template<class U>
  T operator()(U&& u) const {
    return static_cast<T>(std::forward<U>(u));
  }
};

First, while I do have a fundamental understanding of rvalue references, etc. I want to verify if this is the correct way to implement this forward/wrapper ?

Second, to ask if any std or boost library already provide this functionality ?

Extra: would you forward other casts in the same way ?

ACTUAL CASE:

My actual case is to use with boost::range, something like:

//auto targetsRange = mixedRange | boost::adaptors::filtered(TYPECHECK_PRED) | boost::adaptors::transformed(static_cast_forward<TARGET_PTR_TYPE>());

WORKING EXAMPLES:

#include <algorithm>
template<typename T>
struct static_cast_forward {
    template<class U>
    T operator()(U&& u) const {
        return static_cast<T>(std::forward<U>(u));
    }
};

//example 1:

void test1() {
    std::vector<double> doubleVec{1.1, 1.2};
    std::vector<int> intVec;
    std::copy(doubleVec.begin(), doubleVec.end(), intVec.end());//not ok (compiles, but gives warning) 
    std::transform(doubleVec.begin(), doubleVec.end(), std::back_inserter(intVec), static_cast_forward<int>()); //ok
}

//example 2:

struct A {
    virtual ~A() {} 
};

struct B : public A {
};

struct C : public A {
};

void test2() {
    std::vector<A*> vecOfA{ new B, new B};
    std::vector<B*> vecOfB;
    //std::transform(vecOfA.begin(), vecOfA.end(), std::back_inserter(vecOfB), static_cast<B*>); //not ok: syntax error..
    std::transform(vecOfA.begin(), vecOfA.end(), std::back_inserter(vecOfB), static_cast_forward<B*>() ); //ok
}
darune
  • 10,480
  • 2
  • 24
  • 62
  • 1
    Show an example how you want to use `static_cast_forward::operator()`. The way you use `std::forward` is not correct. Template argument should be specified. – Evg Sep 04 '18 at 12:02
  • Basically your function should look like `T&& operator()(U&& u) const { return std::forward(u); }` `std::forward` does a static cast to `T&&` internally. – George Sep 04 '18 at 12:08
  • 1
    @George, you do cast non-pod types in, e.g., CRTP. – Evg Sep 04 '18 at 12:16
  • @Evg If you want to invoke ub. Note that references and pointers are pod types. – George Sep 04 '18 at 12:22
  • @George, pointers, but not references, `static_assert(!std::is_pod_v)`: https://stackoverflow.com/questions/19144007/is-reference-type-in-c-a-pod-type – Evg Sep 04 '18 at 12:25
  • Ah damn, well thanks for correcting me :) "Also, you should never try and cast a type that is non pod and not a reference." – George Sep 04 '18 at 12:27
  • @George, I would also not attempt at casting `int&` to `long&`. ;) – Evg Sep 04 '18 at 12:30
  • @Evg an example could be to use with std::transform to convert one type container to another. https://en.cppreference.com/w/cpp/algorithm/transform. Im using this with boost::range. – darune Sep 04 '18 at 14:16
  • Fixed the missing template argument to std::forward – darune Sep 04 '18 at 14:29
  • Maybe you need this: `copy(make_move_iterator(bs.begin()), make_move_iterator(bs.end()), back_inserter(ds));`? Here `std::vector bs{...};`, `std::vector ds;` and `struct D` has constructor `D(B&&)`. This will move all `bs` into `ds`. – Evg Sep 04 '18 at 14:43

1 Answers1

2

Addition after the question has been clarified.

In both cases you don't need to std::forward anything, because there is nothing to move, you need just a cast. But if you want to generalize to movable types, too, then your implementation seems fine to me. Just don't call it forward because it is not a forward. As far as I know, there is nothing in std to mimic your struct.

So I would just add test3() that really needs moving:

struct B { };

struct D { 
    explicit D(B&&) { }   // Note: explicit!
};

void test3()
{
    std::vector<B> vb{B{}, B{}};
    std::vector<D> vd;

    // Won't compile because constructor is explicit
    //std::copy(std::make_move_iterator(vb.begin()), std::make_move_iterator(vb.end()), std::back_inserter(vd));

    // Works fine
    std::transform(std::make_move_iterator(vb.begin()), std::make_move_iterator(vb.end()), std::back_inserter(vd), static_cast_forward<D>());
}

Answer before question was clarified.

If I correctly understood your intention, then this is what you want:

template<typename T>
struct static_cast_forward {
    template<class U>
    decltype(auto) operator()(U&& u) const
    {
        if constexpr (std::is_lvalue_reference_v<U>)
            return static_cast<T&>(u);
        else
            return static_cast<T&&>(u);
    }
};

Then you have:

struct B { };
struct D : B { };

void foo() {
    D d;
    static_cast_forward<B> scf_as_B;

    static_assert(std::is_same_v<decltype(scf_as_B(D{})), B&&>);
    static_assert(std::is_same_v<decltype(scf_as_B(d)), B&>);
}
darune
  • 10,480
  • 2
  • 24
  • 62
Evg
  • 25,259
  • 5
  • 41
  • 83
  • What I want is basic template for forwarding call of static_cast (since you cannot you use static_cast as a predicate directly). – darune Sep 04 '18 at 14:03
  • Also are you sure that will work ? I believe static_cast returns a copy itself - so wouldn't it return a dangling reference then. eg. static_cast(5) i believe returns a copy - and your else clause return &&. Please explain. – darune Sep 04 '18 at 14:05
  • @darune `static_cast` is just a cast. In my view a *forwarding* function `foo()` should return `int&&` for `foo(5)`, as well as `B&&` for `foo(B{})`. Yes, it can easily become a dangling reference, but this is not a problem of this function, but a problem of a user who uses it incorrectly. You should really present a particular use case you have in mind. Probably, we have an XY problem here: https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – Evg Sep 04 '18 at 14:16
  • maybe you misunderstand the question then, but please explain why you think it is NOT possible to make a fully functioning forward to static_cast ? one that mirrors the underlying call and can be used interchangeably. – darune Sep 04 '18 at 14:23
  • @darune, I just don't understand the requirements. What this function is supposed to do? If you want to forward something you should return either lvalue-ref or rvalue-ref. This is how forwarding is defined. – Evg Sep 04 '18 at 14:26
  • Ok, that is fair, its intended to used to eg. std::transform - to convert between types. https://en.cppreference.com/w/cpp/algorithm/transform. My actual case is to use with boost::range library for a range adapter. Maybe i can provide a small example, but dont have time now. – darune Sep 04 '18 at 14:32