Call return method if method doesn't exist

I want to be able to do the following in C ++

  • Try calling a function from namespace like boost :: filesystem :: copy
  • If copy is not a member of boost :: filesystem then call a fallback function like boost :: filesystem3 :: copy
  • If the fallback function does not exist (this could be either because boost does not have a member filesystem3, or because boost :: filesystem3 does not have a copy of an instance), the code should not compile.

After reading loads and loads with very long and complex code snippets, it is not clear to me what an easy way to do this. C ++ 11 solution is ok. But sometimes the code needs to be compiled with the old version of boost (1.39.0) and that's why this workaround is required.

I am currently doing this by creating a method alias after validating the macro BOOST_VERSION

. But it would be helpful to know a more complex alternative that can be applied to more general cases.

+3


source to share


2 answers


Here is a possible solution with which you can do it.
The only way I have found this is to pollute the namespaces boost::filesystem

and boost::filesystem3

then check if the original function exists. I know this is not the best thing, but it is a trade-off to get it up and running at the end of days.

There are two versions in both namespaces copy

. They are declared as:

void copy(const path& from, const path& to);
void copy(const path& from, const path& to, system::error_code& ec);

      

Note that I have scaled them down with slightly different forms in the example below to keep things simpler and avoid using boost in the example code.

Here's a minimal working example:

#include<iostream>
#include<type_traits>

// original namespaces

namespace boost { namespace filesystem {
    void copy(int, char) { std::cout << "b::f::copy" << std::endl; }
    void copy(int, char, double) {}

    // ... everything else ...
}}

namespace boost { namespace filesystem3 {
    void copy(int, char) { std::cout << "b::f3::copy" << std::endl; }
    void copy(int, char, double) {}

    // ... everything else ...
}}

// pollution

namespace boost { namespace filesystem {
    struct tag {};
    void copy(tag, tag) {}
}}

namespace boost { namespace filesystem3 {
    struct tag {};
    void copy(tag, tag) {}
}}

std::true_type test(int, void(*)(int, char));
std::false_type test(...);

constexpr bool has_filesystem_copy = decltype(test(0, &boost::filesystem::copy))::value;
constexpr bool has_filesystem3_copy = decltype(test(0, &boost::filesystem3::copy))::value;

template<bool = true>
struct fallback_fn {};

template<>
struct fallback_fn<has_filesystem3_copy> {
    template<typename... Args>
    static void invoke(Args... args) {
        boost::filesystem3::copy(args...);
    }
};

template<bool = true>
struct copy_fn: fallback_fn<> {};

template<>
struct copy_fn<has_filesystem_copy> {
    template<typename... Args>
    static void invoke(Args... args) {
        boost::filesystem::copy(args...);
    }
};

int main() {
    copy_fn<>::invoke(0, 'c');
}

      



Feel free to play around with functions that are part of those namespaces marked as original namespaces.
Summarizing:

  • If copy

    available as boost::filesystem

    well as in boost::filesystem3

    the first selected. Take a look at wandbox .

  • If copy

    only available at boost::filesystem

    , it is picked up. Take a look at wandbox .

  • If copy

    only available at boost::filesystem3

    , it is picked up. Take a look at wandbox .

  • If copy

    not available at all, you will get a compile-time error like this:

    'invoke' is not a member of 'copy_fn <>'

    Take a look at wandbox .

To do this, I used template specialization rules and a couple of variables constexpr

.
Note that you can avoid the inclusion <type_traits>

by doing this if you want:

constexpr bool test(int, void(*)(int, char)) { return true; }
constexpr bool test(...) { return false; }

constexpr bool has_filesystem_copy = test(0, &boost::filesystem::copy);
constexpr bool has_filesystem3_copy = test(0, &boost::filesystem3::copy);

      

Again, namespace pollution is not a good idea you might come up with. Anyway, this is a viable approach that probably works in this case if you call copy

through a utility class, for example copy_fn

.
As a side note, keep in mind that this is quite annoying and error prone if you need to wrap multiple functions. This is not the case if I only look at the text of your question, but I do not know what the real case is.

+2


source


Here's another idea that (mostly) does the trick:

#include <tuple>
#include <utility>
#include <iostream>

namespace fake_boost {
    namespace filesystem {
        // class path { public:
        //     template<typename Source> path(Source const&) {}
        // };
        // void copy( const path&, const path& )
        // { std::cout << "fake_boost::filesystem::copy\n"; }
    }
    namespace filesystem3 {
        class path { public:
            template<typename Source> path(Source const&) {}
        };
        void copy( const path&, const path& )
        { std::cout << "fake_boost::filesystem3::copy\n"; }
    }
}

namespace test_copy {
    template <typename...> using void_t = void; // or use C++17 std::void_t
    namespace test_filesystem3 {
        using namespace fake_boost::filesystem3;
        template <typename... Args>
        void do_copy(Args&& ... args)
        { copy(std::forward<Args>(args)...); }
    }
    namespace test_filesystem {
        template <typename Tuple, typename Enable=void>
        struct copy_switcher {
            template <typename... Args>
            static void do_copy(Args&& ... args)
            { test_filesystem3::do_copy(std::forward<Args>(args)...); }
        };
        using namespace fake_boost::filesystem;
        template <typename... Args>
        struct copy_switcher<std::tuple<Args...>,
            void_t<decltype(copy(std::declval<Args&&>()...))>> {
            static void do_copy(Args&& ... args)
            { copy(std::forward<Args>(args)...); }
        };
    }
}

template <typename... Args>
void do_copy(Args&& ... args) {
    test_copy::test_filesystem::copy_switcher<std::tuple<Args...>>
        ::do_copy(std::forward<Args>(args)...);
}

int main() {
    do_copy( "from.txt", "to.txt" );
}

      



A couple of caveats: namespaces must actually exist, but you can always define them as empty. The checked function must not exist in the global scope with compatible arguments. In particular, you cannot rename my last one do_copy

only copy

.

+1


source







All Articles