I want to be able to do the following in C++
After reading loads and loads of extremely long and complicated pieces of code, it is unclear to me what the simple way of doing this would be. A C++11 solution is fine. But the code sometimes requires to compile with an old boost version (1.39.0), which is precisely why this workaround is required.
Currently I do it by creating method alias after checking the BOOST_VERSION macro. But it would be good to know of a more sophisticated alternative that could be applicable for more general cases.
Here is a possible solution with which you can do that.
The only way I found to do that is by polluting the namespaces boost::filesystem and boost::filesystem3, then test if the original function exists. I know that's not the best thing ever, but it's a compromise to have it up and running at the end of the days.
There exist two versions of copy in both the namespaces. 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 redeclared them with slightly different forms in the example below to simplify things up and to avoid using boost in the example code.
Here is 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 with the functions that are part of those namespaces marked as original namespaces.
To sum up:
If copy is available both in boost::filesystem and boost::filesystem3, the former is picked up. See it on wandbox.
If copy is available only in boost::filesystem, it's picked up. See it on wandbox.
If copy is available only in boost::filesystem3, it's picked up. See it on wandbox.
If copy isn't available at all, you get a compile-time error like this:
'invoke' is not a member of 'copy_fn<>'
See it on wandbox.
To do that I used the rules of template specialization and a couple of constexpr variables.
Note that you can avoid to include <type_traits> by doing this, if you prefer:
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, polluting a namespace isn't the best idea with which you can come up. Anyway it's a viable approach that probably works in this case, as long as you invoke copy through an utility class like copy_fn.
As a side note, keep in mind that it's quite annoying and error-prone if you have to wrap more than one function. It's not the case if I look at the text of your question only, but I don't know what's the real case.
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: The namespaces must actually exist, but you can always define them as empty. The function tested for must not exist at global scope with compatible arguments. In particular, you couldn't rename my last do_copy to just copy.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With