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

Solve ambiguity problems #14

Open
JPietrzykTUD opened this issue Feb 28, 2023 · 1 comment
Open

Solve ambiguity problems #14

JPietrzykTUD opened this issue Feb 28, 2023 · 1 comment
Labels
bug Something isn't working enhancement New feature or request help wanted Extra attention is needed

Comments

@JPietrzykTUD
Copy link
Collaborator

As we allow function overloading through functor_name we can end up in a situation where the same function signature is generated twice (e.g., for scalar definition + mask/imask primitives, since the mask type equals the imask type).

Currently, we work around that issue by leaving out the offending definitions. However, this seems to be bad practice.
Thus, we need a better solution.

@JPietrzykTUD JPietrzykTUD added bug Something isn't working enhancement New feature or request help wanted Extra attention is needed labels Feb 28, 2023
@JPietrzykTUD
Copy link
Collaborator Author

JPietrzykTUD commented Mar 24, 2023

The issue of overloading ambiguity occurs if we have an overloaded function with the same parameters. This can happen, e.g., for scalar execution, since the register_type is the same as the imask_type for uint8_t.
So if we have two defined functions like the ones below, the code will not compile since "foo" is defined twice.

namespace functors { 
	template<typename Vec>
	struct foo {
		static auto apply(typename Vec::register_type data) {
			//...
		}
	};

	template<typename Vec>
	struct fooIntegral {
		static auto apply(typename Vec::imask_type data) {
			//...
		}
	};
}
template<typename Vec>
auto foo(typename Vec::register_type data) {
	return functors::foo<Vec>::apply(data);
}
template<typename Vec>
auto foo(typename Vec::imask_type data) {
	return functors::fooIntegral<Vec>::apply(data);
}

To solve this problem, we can use SFINAE like the following (see commit 6e42c4f):

namespace functors {
	// Change 3
	#define TVL_FUNCTORS_FOO_STRUCT_DEFINED
	template<typename Vec> struct foo;

	// Change 3
	#define TVL_FUNCTORS_FOOINTEGRAL_STRUCT_DEFINED
	template<typename Vec> struct fooIntegral;
}
namespace functors { 
	template<typename Vec>
	struct foo {
		// Change 1
		using param_tuple_t = std::tuple<typename Vec::register_type data>;
		static auto apply(typename Vec::register_type data) {
			//...
		}
	};

	template<typename Vec>
	struct fooIntegral {
	        // Change 1
		using param_tuple_t = std::tuple<typename Vec::imask_type data>;
		static auto apply(typename Vec::imask_type data) {
			//...
		}
	};
}
template<typename Vec>
auto foo(typename Vec::register_type data) {
	return functors::foo<Vec>::apply(data);
}
template<
	typename Vec,
	// Change 3
	#ifdef TVL_FUNCTORS_FOO_STRUCT_DEFINED
	// Change 2
	typename std::enable_if_t<
         	!std::is_same_v<
	            typename functors::foo<Vec, Idof>::param_tuple_t,
	            typename functors::fooIntegralVec, Idof>::param_tuple_t>,
         	std::nullptr_t
	> = nullptr
>
auto foo(typename Vec::imask_type data) {
	return functors::fooIntegral<Vec>::apply(data);
}

First, we must determine how to represent the parameters passed into the free function. This can be done by adding a type definition (param_tuple_t) to the implementation-struct storing the parameter type information in a std::tuple (see Change 1).
Now, we can compare the param_tuple_t of the "overloaded" implementation struct with the "original" one and disable the overload if the tuples are equal (see Change 2).
However, as the generator's output depends on the hardware capabilities, we may end up in a situation where the "original" function is not considered for a generation but the "overloaded" one. The std::enable_if_t produces a compilation error since the struct functors::foo would not be declared nor defined.
To cope with this, we introduced a preprocessor macro definition for every implementation-struct and let the compiler disable the check if the "original" struct is not present in the generated library (see Change 3).

As this solution seems to work, it has some caveats.
1. It does not work with parameter packs.
Since not the implementation struct but the static member function is aware of a parameter pack, we cannot retrieve the type information on the struct level. However, I assume that we will not have meaningful overloads for parameter packs in the first place.

2. It only works for 1 overload.
If more than one overload exists, we must compare all function definitions pairwise. However, since the generator is agnostic to the existence/possibility of function overloading (since it is a language-specific feature), we cannot determine what overloads exist within the templates.
If we introduce function overloading into the generator, we could support more than one overload with "the same" parameters. However, currently, I am not convinced whether this is needed.

3. It introduces a ton of preprocessor macros.
Unfortunately, I lack ideas on how to circumvent that. So I guess it just is what it is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

1 participant