Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extended SFINAE capabilities for eckit::Translator #78

Merged
merged 7 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 70 additions & 7 deletions src/eckit/utils/Translator.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <set>
#include <string>
#include <type_traits>
#include <vector>

namespace eckit {
Expand All @@ -29,9 +30,44 @@ namespace eckit {

template <class From, class To>
struct Translator {

// Default template calls cast
To operator()(const From& from) { return To(from); }
// To allow using IsTranslatable with SFINAE it is important the the operator is templated and has a `auto` return type
// C++14 version would be with enable_if_t and decay_t. FDB5 seems not to have migrated to C++17 compilation yet.

#if __cplusplus >= 201402L
// Test for explicit conversion through constructor (also involves implicit conversion or or user-defined conversion operator).
// Note: To(from) is called with () brackets and not with {} because it includes conversion of plain datatypes - MIR is using this
template <typename F, std::enable_if_t<
(!std::is_same<std::decay_t<F>, To>::value && (std::is_same<std::decay_t<decltype(To(std::declval<F>()))>, To>::value)),
bool> = true>
auto operator()(F&& from) {
return To(std::forward<F>(from));
}

// If from and to type are same - simply forward - i.e. allow moving or passing references instead of performing copies
template <typename F, std::enable_if_t<
(std::is_same<std::decay_t<F>, To>::value && !(std::is_lvalue_reference<F>::value && !std::is_const<F>::value)),
bool> = true>
decltype(auto) operator()(F&& from) {
return std::forward<F>(from);
}

// If mutable references are passed, a const ref is returened to avoid modifications downstream...
template <typename F, std::enable_if_t<
(std::is_same<std::decay_t<F>, To>::value && (std::is_lvalue_reference<F>::value && !std::is_const<F>::value)),
bool> = true>
const To& operator()(F&& from) {
return const_cast<const To&>(from);
}
#else
// If conversion is possible
template <typename F, typename std::enable_if<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this enable_if is needed. Probably more useful to just get the compiler error if it doesn't work?

(std::is_same<typename std::decay<decltype(To(std::declval<F>()))>::type, To>::value),
bool>::type
= true>
To operator()(F&& from) {
return To(std::forward<F>(from));
}
#endif
};

// Those are predefined
Expand Down Expand Up @@ -159,12 +195,12 @@ struct Translator<char, std::string> {
};

template <>
struct Translator<std::string, std::vector<std::string> > {
struct Translator<std::string, std::vector<std::string>> {
std::vector<std::string> operator()(const std::string&);
};

template <>
struct Translator<std::string, std::vector<long> > {
struct Translator<std::string, std::vector<long>> {
std::vector<long> operator()(const std::string&);
};

Expand All @@ -179,7 +215,7 @@ struct Translator<std::vector<std::string>, std::string> {
};

template <>
struct Translator<std::string, std::set<std::string> > {
struct Translator<std::string, std::set<std::string>> {
std::set<std::string> operator()(const std::string&);
};

Expand All @@ -188,15 +224,42 @@ struct Translator<std::set<std::string>, std::string> {
std::string operator()(const std::set<std::string>&);
};

template <>
struct Translator<signed char, std::string> {
std::string operator()(signed char v) { return Translator<int, std::string>{}(static_cast<int>(v)); };
};


//----------------------------------------------------------------------------------------------------------------------

// This allows using the Translator without having to explicitly name the type of an argument. For example in case of
// generic string conversion: translate<std::strig>(someVariable)
#if __cplusplus >= 201402L
template <typename To, typename From>
decltype(auto) translate(From&& from) {
return Translator<typename std::decay<From>::type, To>{}(std::forward<From>(from));
}
#else
template <typename To, typename From>
To translate(From&& from) {
return Translator<typename std::decay<From>::type, To>()(std::forward<From>(from));
return Translator<typename std::decay<From>::type, To>{}(std::forward<From>(from));
}
#endif

//----------------------------------------------------------------------------------------------------------------------

#if __cplusplus >= 201703L

// primary template handles types that do not support pre-increment:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is clearly not correct. We are not doing any incrementing here. Pre- or otherwise...

template <typename From, typename To, class = void>
struct IsTranslatable : std::false_type {};

// specialization recognizes types that do support pre-increment:
template <typename From, typename To>
struct IsTranslatable<From, To,
std::void_t<decltype(Translator<From, To>{}(std::declval<const From&>()))>> : std::true_type {};

#endif

//----------------------------------------------------------------------------------------------------------------------

Expand Down
28 changes: 28 additions & 0 deletions src/eckit/utils/VariantHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

//-----------------------------------------------------------------------------

namespace eckit {

// Overload pattern for visiting std::variant using std::visit, see
// https://en.cppreference.com/w/cpp/utility/variant/visit

// The struct Overloaded can have arbitrary many base classes (Ts ...). It publicly inherits from each class and brings
// the call operator (Ts::operator...) of each base class into its scope. The base classes need an overloaded call
// operator (Ts::operator()).


// Overload pattern
template <typename... Ts>
struct Overloaded : Ts... {
using Ts::operator()...;
};

// Explicit deduction guide for the overload pattern above (not needed as of C++20)
template <typename... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>;

} // namespace eckit


//-----------------------------------------------------------------------------