Skip to content

Commit

Permalink
Convert remaining argument / return SFINAE into template argument SFI…
Browse files Browse the repository at this point in the history
…NAE.

When these were a part of the function signature, they ended up being
encoded into the exported symbol. The worst offender was the Debug
operator<< for iterables, for which the mangled name was *half a
kilobyte*.

In total, for CorradeUtility-d.so, the output of `strings | grep
enable_if` went from 24 kB to 5 kB, the rest is std::swap overloads (for
which I need to de-STLify Arguments and Configuration), and
StridedArrayView slice(), which is a bit more complicated matter. The
(stripped) release build doesn't have any exported symbols containing
enable_if, as all those are inline.
  • Loading branch information
mosra committed Aug 12, 2024
1 parent c71547a commit 7b80af8
Show file tree
Hide file tree
Showing 10 changed files with 52 additions and 41 deletions.
16 changes: 8 additions & 8 deletions src/Corrade/Containers/Array.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,17 @@ namespace Implementation {
}
};

template<class T> T* noInitAllocate(std::size_t size, typename std::enable_if<std::is_trivial<T>::value>::type* = nullptr) {
template<class T, typename std::enable_if<std::is_trivial<T>::value, int>::type = 0> T* noInitAllocate(std::size_t size) {
return new T[size];
}
template<class T> T* noInitAllocate(std::size_t size, typename std::enable_if<!std::is_trivial<T>::value>::type* = nullptr) {
template<class T, typename std::enable_if<!std::is_trivial<T>::value, int>::type = 0> T* noInitAllocate(std::size_t size) {
return reinterpret_cast<T*>(new char[size*sizeof(T)]);
}

template<class T> auto noInitDeleter(typename std::enable_if<std::is_trivial<T>::value>::type* = nullptr) -> void(*)(T*, std::size_t) {
template<class T, typename std::enable_if<std::is_trivial<T>::value, int>::type = 0> auto noInitDeleter() -> void(*)(T*, std::size_t) {
return nullptr; /* using the default deleter for T */
}
template<class T> auto noInitDeleter(typename std::enable_if<!std::is_trivial<T>::value>::type* = nullptr) -> void(*)(T*, std::size_t) {
template<class T, typename std::enable_if<!std::is_trivial<T>::value, int>::type = 0> auto noInitDeleter() -> void(*)(T*, std::size_t) {
return [](T* data, std::size_t size) {
if(data) for(T *it = data, *end = data + size; it != end; ++it)
it->~T();
Expand Down Expand Up @@ -1137,23 +1137,23 @@ namespace Implementation {
here in order to ensure proper behavior with function overloads taking more
than one type of (Strided)ArrayView. */
template<class U, class T, class D> struct ArrayViewConverter<U, Array<T, D>> {
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, ArrayView<U>>::type from(Array<T, D>& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static ArrayView<U> from(Array<T, D>& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return {other.data(), other.size()};
}
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, ArrayView<U>>::type from(Array<T, D>&& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static ArrayView<U> from(Array<T, D>&& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return {other.data(), other.size()};
}
};
template<class U, class T, class D> struct ArrayViewConverter<const U, Array<T, D>> {
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, ArrayView<const U>>::type from(const Array<T, D>& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static ArrayView<const U> from(const Array<T, D>& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return {other.data(), other.size()};
}
};
template<class U, class T, class D> struct ArrayViewConverter<const U, Array<const T, D>> {
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, ArrayView<const U>>::type from(const Array<const T, D>& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static ArrayView<const U> from(const Array<const T, D>& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return {other.data(), other.size()};
}
Expand Down
4 changes: 2 additions & 2 deletions src/Corrade/Containers/GrowableArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -1521,11 +1521,11 @@ template<class T, typename std::enable_if<
#endif
}

template<class T> inline void arrayDestruct(T*, T*, typename std::enable_if<std::is_trivially_destructible<T>::value>::type* = nullptr) {
template<class T, typename std::enable_if<std::is_trivially_destructible<T>::value, int>::type = 0> inline void arrayDestruct(T*, T*) {
/* Nothing to do */
}

template<class T> inline void arrayDestruct(T* begin, T* const end, typename std::enable_if<!std::is_trivially_destructible<T>::value>::type* = nullptr) {
template<class T, typename std::enable_if<!std::is_trivially_destructible<T>::value, int>::type = 0> inline void arrayDestruct(T* begin, T* const end) {
/* Needs to be < because sometimes begin > end */
for(; begin < end; ++begin) begin->~T();
}
Expand Down
12 changes: 6 additions & 6 deletions src/Corrade/Containers/StaticArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -1209,19 +1209,19 @@ namespace Implementation {
SFINAE needs to be here in order to ensure proper behavior with function
overloads taking more than one type of (Strided)ArrayView. */
template<class U, std::size_t size, class T> struct ArrayViewConverter<U, StaticArray<size, T>> {
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, ArrayView<U>>::type from(StaticArray<size, T>& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static ArrayView<U> from(StaticArray<size, T>& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return {other.data(), other.size()};
}
};
template<class U, std::size_t size, class T> struct ArrayViewConverter<const U, StaticArray<size, T>> {
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, ArrayView<const U>>::type from(const StaticArray<size, T>& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static ArrayView<const U> from(const StaticArray<size, T>& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return {other.data(), other.size()};
}
};
template<class U, std::size_t size, class T> struct ArrayViewConverter<const U, StaticArray<size, const T>> {
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, ArrayView<const U>>::type from(const StaticArray<size, const T>& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static ArrayView<const U> from(const StaticArray<size, const T>& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return {other.data(), other.size()};
}
Expand All @@ -1234,19 +1234,19 @@ template<std::size_t size, class T> struct ErasedArrayViewConverter<const Static
SFINAE needs to be here in order to ensure proper behavior with function
overloads taking more than one type of StaticArrayView. */
template<class U, std::size_t size, class T> struct StaticArrayViewConverter<size, U, StaticArray<size, T>> {
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, StaticArrayView<size, U>>::type from(StaticArray<size, T>& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static StaticArrayView<size, U> from(StaticArray<size, T>& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return StaticArrayView<size, T>{other.data()};
}
};
template<class U, std::size_t size, class T> struct StaticArrayViewConverter<size, const U, StaticArray<size, T>> {
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, StaticArrayView<size, const U>>::type from(const StaticArray<size, T>& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static StaticArrayView<size, const U> from(const StaticArray<size, T>& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return StaticArrayView<size, const T>(other.data());
}
};
template<class U, std::size_t size, class T> struct StaticArrayViewConverter<size, const U, StaticArray<size, const T>> {
template<class V = U> constexpr static typename std::enable_if<std::is_convertible<T*, V*>::value, StaticArrayView<size, const U>>::type from(const StaticArray<size, const T>& other) {
template<class V = U, typename std::enable_if<std::is_convertible<T*, V*>::value, int>::type = 0> constexpr static StaticArrayView<size, const U> from(const StaticArray<size, const T>& other) {
static_assert(sizeof(T) == sizeof(U), "types are not compatible");
return StaticArrayView<size, const T>(other.data());
}
Expand Down
4 changes: 2 additions & 2 deletions src/Corrade/Containers/String.h
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ class CORRADE_UTILITY_EXPORT String {
#else
/* Gets ambigous when calling String{ptr, 0}. FFS, zero as null pointer
was deprecated in C++11 already, why is this still a problem?! */
template<class T> String(typename std::enable_if<std::is_convertible<T, Deleter>::value && !std::is_convertible<T, std::size_t>::value, char*>::type data, T deleter) noexcept: String{deleter, nullptr, data} {}
template<class T, typename std::enable_if<std::is_convertible<T, Deleter>::value && !std::is_convertible<T, std::size_t>::value, int>::type = 0> String(char* data, T deleter) noexcept: String{deleter, nullptr, data} {}
#endif

/**
Expand Down Expand Up @@ -566,7 +566,7 @@ class CORRADE_UTILITY_EXPORT String {
/* Gets ambigous when calling String{nullptr, 0}. FFS, zero as null
pointer was deprecated in C++11 already, why is this still a
problem?! */
template<class T> String(typename std::enable_if<std::is_convertible<T, Deleter>::value && !std::is_convertible<T, std::size_t>::value, std::nullptr_t>::type, T) noexcept = delete;
template<class T, typename std::enable_if<std::is_convertible<T, Deleter>::value && !std::is_convertible<T, std::size_t>::value, int>::type = 0> String(std::nullptr_t, T) noexcept = delete;
#endif

/**
Expand Down
14 changes: 12 additions & 2 deletions src/Corrade/Cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -1131,12 +1131,22 @@ template<unsigned int value> struct Tags {
constexpr explicit Tags(InitT) {}

/* Conversion from other tag combination, allowed only if the other is
not a subset */
not a subset. MSVC 2015 and 2017 cannot handle SFINAE in the template,
putting it in the argument instead. */
#ifndef CORRADE_MSVC2017_COMPATIBILITY
template<unsigned int otherValue, typename std::enable_if<IsTagConversionAllowed<value, otherValue>::Value, int>::type = 0> constexpr Tags(Tags<otherValue>) {}
#else
template<unsigned int otherValue> constexpr Tags(Tags<otherValue>, typename std::enable_if<IsTagConversionAllowed<value, otherValue>::Value>::type* = {}) {}
#endif

/* Conversion from a single tag, allowed only if we're a single bit and
the other is not a subset */
the other is not a subset. MSVC 2015 and 2017 cannot handle SFINAE in
the template, putting it in the argument instead. */
#ifndef CORRADE_MSVC2017_COMPATIBILITY
template<class T, typename std::enable_if<IsSingleTagConversionAllowed<Value, TypeTraits<T>::Index>::Value, int>::type = 0> constexpr Tags(T) {}
#else
template<class T> constexpr Tags(T, typename std::enable_if<IsSingleTagConversionAllowed<Value, TypeTraits<T>::Index>::Value>::type* = {}) {}
#endif

/* A subset of operators on Features, excluding the assignment ones --
since they modify the type, they make no sense here */
Expand Down
8 changes: 4 additions & 4 deletions src/Corrade/TestSuite/Comparator.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,12 @@ template<class T, class, class> struct ComparatorTraits: ComparatorOperatorTrait

CORRADE_HAS_TYPE(CanSaveDiagnostic, decltype(std::declval<T>().saveDiagnostic({}, std::declval<Utility::Debug&>(), {})));

template<class T> auto diagnosticSaver(typename std::enable_if<CanSaveDiagnostic<Comparator<T>>::value>::type* = nullptr) -> void(*)(void*, ComparisonStatusFlags, Utility::Debug& out, const Containers::StringView&) {
template<class T, typename std::enable_if<CanSaveDiagnostic<Comparator<T>>::value, int>::type = 0> auto diagnosticSaver() -> void(*)(void*, ComparisonStatusFlags, Utility::Debug& out, const Containers::StringView&) {
return [](void* comparator, ComparisonStatusFlags flags, Utility::Debug& out, const Containers::StringView& path) {
static_cast<Comparator<T>*>(comparator)->saveDiagnostic(flags, out, path);
};
}
template<class T> auto diagnosticSaver(typename std::enable_if<!CanSaveDiagnostic<Comparator<T>>::value>::type* = nullptr) -> void(*)(void*, ComparisonStatusFlags, Utility::Debug& out, const Containers::StringView&) {
template<class T, typename std::enable_if<!CanSaveDiagnostic<Comparator<T>>::value, int>::type = 0> auto diagnosticSaver() -> void(*)(void*, ComparisonStatusFlags, Utility::Debug& out, const Containers::StringView&) {
return nullptr;
}

Expand All @@ -328,10 +328,10 @@ constexpr ComparisonStatusFlags comparisonStatusFlags(ComparisonStatusFlags valu
return value;
}
CORRADE_HAS_TYPE(HasOldPrintErrorMessage, decltype(std::declval<T>().printErrorMessage(std::declval<Utility::Error&>(), {}, {})));
template<class T> CORRADE_DEPRECATED("use printMessage() in custom Comparator implementations instead") void printMessage(typename std::enable_if<HasOldPrintErrorMessage<T>::value, T&>::type comparator, ComparisonStatusFlags, Utility::Debug& out, const char* actual, const char* expected) {
template<class T, typename std::enable_if<HasOldPrintErrorMessage<T>::value, int>::type = 0> CORRADE_DEPRECATED("use printMessage() in custom Comparator implementations instead") void printMessage(T& comparator, ComparisonStatusFlags, Utility::Debug& out, const char* actual, const char* expected) {
comparator.printErrorMessage(static_cast<Utility::Error&>(out), actual, expected);
}
template<class T> void printMessage(typename std::enable_if<!HasOldPrintErrorMessage<T>::value, T&>::type comparator, ComparisonStatusFlags flags, Utility::Debug& out, const char* actual, const char* expected) {
template<class T, typename std::enable_if<!HasOldPrintErrorMessage<T>::value, int>::type = 0> void printMessage(T& comparator, ComparisonStatusFlags flags, Utility::Debug& out, const char* actual, const char* expected) {
comparator.printMessage(flags, out, actual, expected);
}
#endif
Expand Down
13 changes: 5 additions & 8 deletions src/Corrade/Utility/Debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -914,14 +914,11 @@ a nested iterable type, the values are separated by newlines. Specifying
@ref Debug::Flag::Packed or using @ref Debug::packed will print the values
tightly-packed without commas and spaces in between.
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
template<class Iterable> Debug& operator<<(Debug& debug, const Iterable& value)
#else
/* libc++ from Apple's Clang "4.2" (3.2-svn) doesn't have constexpr operator
bool for std::integral_constant, thus we need to use ::value instead */
template<class Iterable> Debug& operator<<(typename std::enable_if<IsIterable<Iterable>::value && !IsStringLike<Iterable>::value, Debug&>::type debug, const Iterable& value)
#endif
{
template<class Iterable
#ifndef DOXYGEN_GENERATING_OUTPUT
, typename std::enable_if<IsIterable<Iterable>::value && !IsStringLike<Iterable>::value, int>::type = 0
#endif
> Debug& operator<<(Debug& debug, const Iterable& value) {
/* True if the values themselves are also containers. A string is
technically a container too, but printing it as separate chars would be
silly. */
Expand Down
8 changes: 6 additions & 2 deletions src/Corrade/Utility/DebugStl.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ the stream instead, assuming it's a cheaper operation than conversion to a
#ifdef DOXYGEN_GENERATING_OUTPUT
Debug& operator<<(Debug& debug, const std::string& value);
#else
template<class T> typename std::enable_if<std::is_same<typename std::decay<T>::type, std::string>::value || (std::is_convertible<T, std::string>::value && !Implementation::HasOstreamOutput<T>::value), Debug&>::type operator<<(Debug& debug, const T& value) {
template<class T, typename std::enable_if<std::is_same<typename std::decay<T>::type, std::string>::value || (std::is_convertible<T, std::string>::value && !Implementation::HasOstreamOutput<T>::value), int>::type = 0> Debug& operator<<(Debug& debug, const T& value) {
return Implementation::debugPrintStlString(debug, value);
}
#endif
Expand All @@ -79,7 +79,11 @@ template<class T> typename std::enable_if<std::is_same<typename std::decay<T>::t
All other types than exactly @ref std::string are printed as containers.
*/
template<class T> typename std::enable_if<!std::is_same<T, char>::value, Debug&>::type operator<<(Debug& debug, const std::basic_string<T>& value) {
template<class T
#ifndef DOXYGEN_GENERATING_OUTPUT
, typename std::enable_if<!std::is_same<T, char>::value, int>::type = 0
#endif
> Debug& operator<<(Debug& debug, const std::basic_string<T>& value) {
return debug << Containers::ArrayView<const T>{value.data(), value.size()};
}

Expand Down
2 changes: 1 addition & 1 deletion src/Corrade/Utility/JsonWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ class CORRADE_UTILITY_EXPORT JsonWriter {
#ifdef DOXYGEN_GENERATING_OUTPUT
JsonWriter& write(bool value);
#else
template<class T> typename std::enable_if<std::is_same<T, bool>::value, JsonWriter&>::type write(T value) {
template<class T, typename std::enable_if<std::is_same<T, bool>::value, int>::type = 0> JsonWriter& write(T value) {
return writeBoolInternal(value);
}
#endif
Expand Down
12 changes: 6 additions & 6 deletions src/Corrade/Utility/Memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,29 +168,29 @@ template<class T, std::size_t alignment = alignof(T)> Containers::Array<T> alloc
namespace Implementation {

#ifdef CORRADE_TARGET_WINDOWS
template<class T> void alignedDeleter(typename std::enable_if<std::is_trivially_destructible<T>::value, T>::type* const data, std::size_t) {
template<class T, typename std::enable_if<std::is_trivially_destructible<T>::value, int>::type = 0> void alignedDeleter(T* const data, std::size_t) {
_aligned_free(data);
}
template<class T> void alignedDeleter(typename std::enable_if<!std::is_trivially_destructible<T>::value, T>::type* const data, std::size_t size) {
template<class T, typename std::enable_if<!std::is_trivially_destructible<T>::value, int>::type = 0> void alignedDeleter(T* const data, std::size_t size) {
for(std::size_t i = 0; i != size; ++i) data[i].~T();
_aligned_free(data);
}
#else
template<class T> void alignedDeleter(typename std::enable_if<std::is_trivially_destructible<T>::value, T>::type* const data, std::size_t) {
template<class T, typename std::enable_if<std::is_trivially_destructible<T>::value, int>::type = 0> void alignedDeleter(T* const data, std::size_t) {
std::free(data);
}
template<class T> void alignedDeleter(typename std::enable_if<!std::is_trivially_destructible<T>::value, T>::type* const data, std::size_t size) {
template<class T, typename std::enable_if<!std::is_trivially_destructible<T>::value, int>::type = 0> void alignedDeleter(T* const data, std::size_t size) {
for(std::size_t i = 0; i != size; ++i) data[i].~T();
std::free(data);
}
#ifndef CORRADE_TARGET_UNIX
template<class T> void alignedOffsetDeleter(typename std::enable_if<std::is_trivially_destructible<T>::value, T>::type* const data, std::size_t) {
template<class T, typename std::enable_if<std::is_trivially_destructible<T>::value, int>::type = 0> void alignedOffsetDeleter(T* const data, std::size_t) {
/* Using a unsigned byte in order to be able to represent a 255 byte offset
as well */
std::uint8_t* const dataChar = reinterpret_cast<std::uint8_t*>(data);
std::free(dataChar - *(dataChar -1));
}
template<class T> void alignedOffsetDeleter(typename std::enable_if<!std::is_trivially_destructible<T>::value, T>::type* const data, std::size_t size) {
template<class T, typename std::enable_if<!std::is_trivially_destructible<T>::value, int>::type = 0> void alignedOffsetDeleter(T* const data, std::size_t size) {
for(std::size_t i = 0; i != size; ++i) data[i].~T();

/* Using a unsigned byte in order to be able to represent a 255 byte offset
Expand Down

0 comments on commit 7b80af8

Please sign in to comment.