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 4 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
1 change: 1 addition & 0 deletions src/eckit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ list( APPEND eckit_utils_srcs
utils/MD5.h
utils/EnumBitmask.h
utils/Optional.h
utils/Overloaded.h
utils/RLE.cc
utils/RLE.h
utils/Regex.cc
Expand Down
28 changes: 28 additions & 0 deletions src/eckit/utils/Overloaded.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


//-----------------------------------------------------------------------------
7 changes: 7 additions & 0 deletions src/eckit/utils/Translator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,19 @@ char Translator<std::string, char>::operator()(const std::string& s) {
return s[0];
}


std::string Translator<char, std::string>::operator()(char c) {
std::string s;
s = c;
return s;
}


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


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

} // namespace eckit
52 changes: 45 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 @@ -30,8 +31,27 @@ namespace eckit {
template <class From, class To>
struct Translator {

// Default template calls cast
To operator()(const From& from) { return To(from); }
// 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_v<std::decay_t<F>, To> && std::is_constructible_v<To, F>),
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_v<std::decay_t<F>, To>,
bool> = true>
decltype(auto) operator()(F&& from) {
if constexpr (std::is_lvalue_reference_v<F> && !std::is_const_v<F>) {
return const_cast<const To&>(from);
}
else {
return std::forward<F>(from);
}
}
};

// Those are predefined
Expand Down Expand Up @@ -159,12 +179,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 +199,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,18 +208,36 @@ 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);
};


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

// 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)
template <typename To, typename From>
To translate(From&& from) {
return Translator<typename std::decay<From>::type, To>()(std::forward<From>(from));
decltype(auto) translate(From&& from) {
return Translator<std::decay_t<From>, To>{}(std::forward<From>(from));
}

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


// 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 {};


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

} // namespace eckit

#endif
7 changes: 7 additions & 0 deletions tests/utils/test_translator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ CASE("Translate strings to double") {
EXPECT_THROWS_AS(t("foo555bar"), BadParameter);
}


CASE("Translate signed char as a number") {
Translator<signed char, std::string> t;

EXPECT(t((signed char)127) == std::string("127"));
}

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

} // namespace eckit::test
Expand Down
Loading