Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum
well, poo poo

raminasi posted:

did anyone else start saying "oh what no no no no" to themselves while reading that

mostly I was thinking "why would anyone use templates this poo poo is nuts"

Adbot
ADBOT LOVES YOU

Volte
Oct 4, 2004

woosh woosh
having to make a variant in C++ reminds me of cutthroat kitchen when someone has to make an omelette with only a hammer and a guitar or something

sarehu
Apr 20, 2007

(call/cc call/cc)

hackbunny posted:

ok, let's step back and rationalize. implementing variant, even a lovely buggy limited version of it, takes a huge amount of code, almost all of it metaprogramming. what does variant do?[list]
[*]it contains another object, of any of the types specified in its parameter list. templates can generate a lot of compile time type information, code and data, but a thing they can't create is new names for things. variant can't, therefore, contain a union, because we'd have to create a union field for each of the types, and fields have to be named, and metaprogramming can't name things. so, ironically, the standard discriminated union container is not, in fact, a discriminated union (unlike say, array which is a wrapper for a C array, vector which is a wrapper for a dynamically sized C array, string, etc.). what we need, instead, is a blob (like say, a char array) of the appropriate size and alignment to contain any of the possible types. type_traits includes just such a blob type, aligned_union. pity because of all the things variant requires, this was the only one I already knew how to write

You can use a union. You want to use something like this internally.

code:
template <class... Args>
union variant_union;

template <class First, class... Rest>
union variant_union<First, Rest...> {
    First head;
    variant_union<Rest...> tail;
};

template <>
union variant_union<> {
    // empty union
};

JawnV6
Jul 4, 2004

So hot ...

Bloody posted:

with the power of Icarus Verilog, your Verilog can indeed be computer instructions!!!
last time i tried their DPI flavors conflicted with whatever synopsys hooks the code base was using, and there still isn't 100% support for 2009 system verilog (which may, in fact, be a programming language)

Bloody
Mar 3, 2013

I just assume that nothing supports sysv and write Verilog-2001. so far this has been a mostly valid assumption

(note that I am trapped in the hellscape of actel/microsemi FPGAs)

VikingofRock
Aug 24, 2008




hackbunny posted:

ok, let's step back and rationalize. implementing variant, even a lovely buggy limited version of it, takes a huge amount of code, almost all of it metaprogramming.

I'm loving these posts.

Fergus Mac Roich
Nov 5, 2008

Soiled Meat

Sweeper posted:

well, poo poo


mostly I was thinking "why would anyone use templates this poo poo is nuts"

Well lemme ask this could you create a flexible compile time type safe discriminated union with C# generics?

Athas
Aug 6, 2007

fuck that joker

qntm posted:

I was going to make a programming language with no I/O capability whatsoever, and adding such would be specifically forbidden in the licence

it would be fully-featured, but the compiler would optimise every program down to a no-op

There are plenty of languages with no I/O whatsoever (and not like Haskell's "whoops, there's actually a whole imperative sublanguage in here"). They usually compile to functions that you then have to call from some other language.

VikingofRock
Aug 24, 2008




C++ templates are basically eldritch magic. Very arcane but very powerful, and best suited for the hands of wizards.

DONT THREAD ON ME
Oct 1, 2002

by Nyc_Tattoo
Floss Finder
hackbunny i dont know anything about templates or c++ but I was able to follow (most) of what you were talking about there. my point is you're a good and clear writer and I enjoy your posts.

Wheany
Mar 17, 2006

Spinyahahahahahahahahahahahaha!

Doctor Rope

Malcolm XML posted:

a miserable little pile of secrets

i'm going to make a password manager named "aman" and then use that line as the first answer in the faq

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.

MALE SHOEGAZE posted:

hackbunny i dont know anything about templates or c++ but I was able to follow (most) of what you were talking about there. my point is you're a good and clear writer and I enjoy your posts.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

Fergus Mac Roich posted:

Well lemme ask this could you create a flexible compile time type safe discriminated union with C# generics?

something like this? https://github.com/mcintyre321/OneOf

Xarn
Jun 26, 2015

What if I want 10 types in a variant?

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

Xarn posted:

What if I want 10 types in a variant?

bug the library author to add some more definitions

also if it's good enough for tuple it's good enough for me!

Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

Xarn posted:

What if I want 10 types in a variant?

guess you have to use cpp then, sorry

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av

MALE SHOEGAZE posted:

hackbunny i dont know anything about templates or c++ but I was able to follow (most) of what you were talking about there. my point is you're a good and clear writer and I enjoy your posts.

thank you, I try :tipshat:

current progress: just learned how to use enable_if and how to use it with copy and move constructors. slurps mad rips this is your cue to start laughing your rear end off

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av
gj using aligned_union instead of aligned_union_t hackbunny :suicide:
thank god the undefined behavior sanitizer caught one of its side effects. it's really annoying that you can't use the address sanitizer and the memory sanitizer at the same time (but I'm grateful to have them)

BobHoward
Feb 13, 2012

The only thing white people deserve is a bullet to their empty skull

hackbunny posted:

thank you, I try :tipshat:

current progress: just learned how to use enable_if and how to use it with copy and move constructors. slurps mad rips this is your cue to start laughing your rear end off

speaking of which i keep waiting for the groundswell of people laughing their rear end off at you for missing the True Way To Use C++ that doesn't run into all these insane things

and then i remember, oh yeah. c++

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

Xarn posted:

What if I want 10 types in a variant?

the actual downside of it as compared to the c++ thing is that it requires boxing the values since it just stuffs everything in an object field

BobHoward posted:

speaking of which i keep waiting for the groundswell of people laughing their rear end off at you for missing the True Way To Use C++ that doesn't run into all these insane things

and then i remember, oh yeah. c++

some of the problems are self-inflicted to various degress. the way to make allocators not awful is just not use allocators because they only exist for the sake of segmented memory and are shockingly useless for anything else. a lot of the other complexity you could ignore and get a solution that's not worse than what's possible in other languages.

Slurps Mad Rips
Jan 25, 2009

Bwaltow!

hackbunny posted:

current progress: just learned how to use enable_if and how to use it with copy and move constructors. slurps mad rips this is your cue to start laughing your rear end off

yeah im not gonna laugh at that because when enable_if/SFINAE clicked it was several years ago after staring at it for weeks. i feel like with c++ template metaprogramming theres a mental state before and after understanding and using enable_if and i cannot get into that pre dark magick mental state and i legit wonder sometimes if i would be happier not knowing what i know but then i remember that i implemented interface restraints for concepts entirely in c++14 and i realize im in too deep for it to matter.

hackbunny posted:

gj using aligned_union instead of aligned_union_t hackbunny :suicide:

when i wrote my first optional type i used aligned_storage instead of aligned_storage_t so welcome to the club

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av

Slurps Mad Rips posted:

yeah im not gonna laugh at that because when enable_if/SFINAE clicked it was several years ago after staring at it for weeks. i feel like with c++ template metaprogramming theres a mental state before and after understanding and using enable_if and i cannot get into that pre dark magick mental state and i legit wonder sometimes if i would be happier not knowing what i know but then i remember that i implemented interface restraints for concepts entirely in c++14 and i realize im in too deep for it to matter.

well turns out I was wrong and I didn't actually get it. welp! effortpost soon

30 TO 50 FERAL HOG
Mar 2, 2005



is there such a thing as an enum with multiple properties? (c# btw)

code:
public enum Command
{
	Power = "PR"
}
i cant actually do this because enums cant have string values :negative:

even if i could, i cant have other properties like max value/min value

i could use static classes

code:
public static class Power
{
	public static string Value = "PR";
	public static int Min = 0;
	public static int Max = 1;
}
but if i do this i cant use inheritance

code:
public static class Command
{
}

public static class Power : Command
{
	public static string Value = "PR";
	public static int Min = 0;
	public static int Max = 1;
}

public void Execute(Command fizzbuzz)
{
	//do stuff
}

30 TO 50 FERAL HOG fucked around with this message at 21:25 on Dec 1, 2016

suffix
Jul 27, 2013

Wheeee!
i think in java you need libraries like guava and autovalue to hide the tedious simplicity, and in c++ you need libraries like boost to hide the hellish complexity

VikingofRock
Aug 24, 2008




suffix posted:

i think in java you need libraries like guava and autovalue to hide the tedious simplicity, and in c++ you need libraries like boost to hide the hellish complexity

I like this

HappyHippo
Nov 19, 2003
Do you have an Air Miles Card?
do you mean like sum types? because those are the best. do the enums in rust look like what you want?

30 TO 50 FERAL HOG
Mar 2, 2005



yeah rust enums look handy

i ended up doing this

code:
public class Command
{
	private string m_CommandString;
	private int m_MinValue;
	private int m_MaxValue;

	private Command(string CommandString, int MinValue, int MaxValue)
	{
		m_CommandString = CommandString;
		m_MinValue = MinValue;
		m_MaxValue = MaxValue;
	}

	public static Command Power
	{
		get
		{
			return new Command(@"PR", 0, 1);
		}
	}
}

HappyHippo
Nov 19, 2003
Do you have an Air Miles Card?
yeah sum types/tagged unions are very good. in languages that don't have them people try to emulate them by overloading the meaning of null or other values and it's just gross and causes so many bugs. like whenever you see poo poo like "returns the index of the first occurrence of a character, or -1 if the character doesn't occur" or "returns a File, or null if the file wasn't found" that could have been done properly with a tagged union.

Shaggar
Apr 26, 2006

BiohazrD posted:

is there such a thing as an enum with multiple properties? (c# btw)

code:
public enum Command
{
	Power = "PR"
}
i cant actually do this because enums cant have string values :negative:

even if i could, i cant have other properties like max value/min value

i could use static classes

code:
public static class Power
{
	public static string Value = "PR";
	public static int Min = 0;
	public static int Max = 1;
}
but if i do this i cant use inheritance

code:
public static class Command
{
}

public static class Power : Command
{
	public static string Value = "PR";
	public static int Min = 0;
	public static int Max = 1;
}

public void Execute(Command fizzbuzz)
{
	//do stuff
}

no. c# enums loving suck rear end. your solution is fine, but you'll miss out on things like iteration of possible values unless you do some gross hacks (either another static prop w/ list of options or reflection)

Soricidus
Oct 21, 2010
freedom-hating statist shill

suffix posted:

i think in java you need libraries like guava and autovalue to hide the tedious simplicity, and in c++ you need libraries like boost to hide the hellish complexity

you know you're a c++ programmer when you look at boost and think "yes, this is hiding the hellish complexity"

Shaggar
Apr 26, 2006

BiohazrD posted:

yeah rust enums look handy

i ended up doing this

code:
public class Command
{
	private string m_CommandString;
	private int m_MinValue;
	private int m_MaxValue;

	private Command(string CommandString, int MinValue, int MaxValue)
	{
		m_CommandString = CommandString;
		m_MinValue = MinValue;
		m_MaxValue = MaxValue;
	}

	public static Command Power
	{
		get
		{
			return new Command(@"PR", 0, 1);
		}
	}
}

change the prop to:
C# code:
	public static Command Power { get; } = new Command(@"PR", 0, 1);
otherwise it will create a new copy of the thing every time you access it.

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Slurps Mad Rips posted:

yeah im not gonna laugh at that because when enable_if/SFINAE clicked it was several years ago after staring at it for weeks. i feel like with c++ template metaprogramming theres a mental state before and after understanding and using enable_if and i cannot get into that pre dark magick mental state and i legit wonder sometimes if i would be happier not knowing what i know but then i remember that i implemented interface restraints for concepts entirely in c++14 and i realize im in too deep for it to matter.


when i wrote my first optional type i used aligned_storage instead of aligned_storage_t so welcome to the club

i miss C++ so bad :saddowns:

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

Soricidus posted:

you know you're a c++ programmer when you look at boost and think "yes, this is hiding the hellish complexity"

you're a real c++ programmer when you look at MPL and not only understand what it does but are able to simply code you wrote using it.

(less true as of c++11 thankfully)

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av
let's talk about enable_if. enable_if is a metafunction with two arguments: a boolean, and a type. if the boolean is true, it returns the type, otherwise it returns nothing. it's used to conditionally cause a non-critical compilation error (called substitution failure), which is used to conditionally exclude functions from overload resolution. it's a sad excuse for pattern matching, but not as bad as the alternatives it replaced. let's see an example:
C++ code:
template<class Iter>
void molest(Iter& it)
{
	-- it;
}
this generic function molests an iterator (iterators are a generalization of pointers, used both for addressing elements in containers and for I/O). the molestation consists of decrementing the iterator, but not all iterators can be decremented: a pointer certainly can, but a stream input iterator can only go forwards. if we want to define molestation differently for non-decrementable iterators, we need to overload the molest function. overloading is the basis of pattern matching in c++, and it's bad at it because it's not what it was designed for. this didn't stop generations of c++ programmers from finding horribly convoluted ways to bend function overloading into something that resembled pattern matching

an old way, widely used, was tag dispatch: instead of overloading the function, we make it fully generic; we pass the generic types to metafunctions that classify them according to patterns, and return a different type (the tag) for each pattern; we then use the tag to select the right overload of the internal implementation of the function. optimization of iterator algorithms is heavily based on tag dispatch, let's see what it looks like:
C++ code:
namespace impl { // or "detail", "bits", etc. there isn't a standard convention
	template<class Iter>
	// std::forward_iterator_tag derives from input_iterator_tag, so this will
	// match both
	void molest(Iter& it, std::forward_iterator_tag)
	{
		++ it;
	}

	template<class Iter>
	// std::output_iterator_tag is unrelated to other tags though! so it has to
	// be handled separately, even if it could share the forward/input iterator
	// implementation above
	void molest(Iter& it, std::output_iterator_tag)
	{
		++ it;
	}

	// and this is the "else"
	template<class Iter, class AnyTag>
	void molest(Iter& it, AnyTag)
	{
		-- it;
	}
}

template<class Iter>
void molest(Iter& it)
{
	// typename here tells the compiler that iterator_category is a nested type
	// name, not a static member (the syntax is the same and it's ambiguous in
	// the context of templates, because a specialization of iterator_traits
	// might, conceivably, define iterator_category as a member)

	impl::molest(it, typename std::iterator_traits<Iter>::iterator_category());
	// what we do here is call the iterator_traits metafunction on the iterator
	// type, get its iterator_category return value, which is a type, and
	// construct an instance of said type. the instance is completely stateless
	// and has in effect no value: all we use is its type, to pick the right
	// overload of impl::molest
}
we can make it suck a little less by defining our own tagging metafunction instead of using iterator_traits:
C++ code:
namespace impl {
	struct molest_by_increment_tag {};
	struct molest_by_decrement_tag {};

	template<class Iter>
	void molest(Iter& it, molest_by_increment_tag)
	{
		++ it;
	}

	template<class Iter>
	void molest(Iter& it, molest_by_decrement_tag)
	{
		-- it;
	}

	template<class Iter>
	struct molestability_of
	{
	private:
		using iterator_category = typename std::iterator_traits<Iter>::iterator_category;

	public:
		using type = std::conditional_t<
			std::is_same<iterator_category, std::input_iterator_tag>::value ||
			std::is_same<iterator_category, std::forward_iterator_tag>::value ||
			std::is_same<iterator_category, std::output_iterator_tag>::value,
			molest_by_increment_tag,
			molest_by_decrement_tag
		>;
	};

	// metafunction syntax is hideous. the horror above, if it could be written in a sane way:
	/*
	class molestability_of(class Iter)
	{
		class iterator_category = std::iterator_traits(Iter).iterator_category;

		if (iterator_category == std::input_iterator_tag ||
		    iterator_category == std::forward_iterator_tag ||
		    iterator_category == std::output_iterator_tag)
		{
			return molest_by_increment_tag;
		}
		else {
			return molest_by_decrement_tag;
		}
	}
	*/
}

template<class Iter>
void molest(Iter& it)
{
	impl::molest(it, typename impl::molestability_of<Iter>::type());
}
oh jesus christ, gently caress, no, that's even worse, what the hell is wrong with this language. well, let's just say that tag dispatch has its limits. nowadays, the preferred way to pattern match is sfinae, an acronym meaning Substitution Failure Is Not An Error. as the name suggests, it's literally a hack: we cause recoverable compilation errors as a hint that the compiler should go look elsewhere. the main tool for using sfinae is the enable_if metafunction:
C++ code:
namespace impl {
	template<class Iter>
	using iterator_category_t = typename std::iterator_traits<Iter>::iterator_category;

	template<class Iter>
	constexpr bool molest_by_increment_v =
		std::is_same<iterator_category_t<Iter>, std::input_iterator_tag>::value ||
		std::is_same<iterator_category_t<Iter>, std::forward_iterator_tag>::value ||
		std::is_same<iterator_category_t<Iter>, std::output_iterator_tag>::value
	;
}

template<class Iter>
// this is how enable_if is used. god is dead. enable_if is basically a no-op:
// if the condition is true, it evaluates to Iter, and the function gains a
// second, anonymous argument of type Iter *, defaulted to nullptr (be careful
// when writing anonymous defaulted pointer arguments and remember to leave a
// space between the * of the pointer and the = of the default value, because
// *= is a separate token that's not valid here. took me way too long to realize
// this). if the condition evaluates to false, enable_if returns nothing,
// the expansion of enable_if_t fails and this particular overload becomes non-
// viable ("ill-formed" I think is the term) and is excluded from resolution
void molest(Iter& it, std::enable_if_t<impl::molest_by_increment_v<Iter>, Iter> * = nullptr)
{
	++ it;
}

template<class Iter>
// even if enable_if succeeds in both overloads, they'll still be considered
// distinct overloads, despite both having a prototype of void(Iter&, Iter*):
// they'll just be ambiguous overloads, and overload resolution will fail. on
// the other hand, if their generic prototypes were exactly identical, it would
// be an outright compilation error, even if the function was never referenced
void molest(Iter& it, std::enable_if_t<!impl::molest_by_increment_v<Iter>, Iter> * = nullptr)
{
	-- it;
}
and this is how enable_if is used! it can be used as an additional argument type (as we've done here), as a return type (can't be done here because the return type doesn't participate in overload resolution and we'd get a redefinition error), as an argument type (can't be done here or the compiler wouldn't be able to deduce Iter), as an additional template parameter (can't be done here, redefinition error because both overloads would be void(Iter&)), as a base class, etc. as long as it's used in a template function or class. sometimes we'll have to use it to control the pattern matching of functions that explicitly can't be templates, and things will get a little complicated (that is, even more complicated)

bonus content: if we need to disable a single function, instead of choosing among several overloads, there's a much simpler way that's, again, a terrible hack
C++ code:
// if we need to check that a template parameter supports a certain operation,
// well we can simply check that it supports that operation! we use declval to
// conjure a pseudo-instance out of the void, perform the operation on it, and
// take the type of the expression with decltype: if the operation isn't
// supported, the expression will have no type, decltype will fail, it will be
// considered a substitution failure, and it will be like the decrement function
// didn't exist. try it with a forward_list iterator for example: the error will
// be "no matching function", exactly like no appropriate overload was found.
// enable_if has compiler magic, though, and will generally give better error
// messages
template<class Iter, class = decltype(--std::declval<Iter&>())>
void decrement(Iter& it)
{
	-- it;
}
I wrote "it will be considered a substitution failure": did I mean that not all errors are considered substitution failures? exactly. next time we'll see when an error is an error, and how to force it to be a substitution failure instead

Deep Dish Fuckfest
Sep 6, 2006

Advanced
Computer Touching


Toilet Rascal

hackbunny posted:

nowadays, the preferred way to pattern match is sfinae, an acronym meaning Substitution Failure Is Not An Error. as the name suggests, it's literally a hack: we cause recoverable compilation errors as a hint that the compiler should go look elsewhere. the main tool for using sfinae is the enable_if metafunction:

at least since c++11 there's enable_if to hide the horrors that underlie this whole thing. i've had to use "naked" sfinae once to get around limitations in code generation tools in a c++03 codebase and i've not felt clean since then

Slurps Mad Rips
Jan 25, 2009

Bwaltow!

hackbunny posted:

a p. drat fine :coffee: write up

i just wanted to point out that you can check if an iterator (that follows the iterator category system) has any of a number of tags with std::is_base_of. for example,
C++ code:
template <class T>
using iterator_category = typename std::iterator_traits<T>::iterator_category;
template <class T>
constexpr bool increment_v = not std::is_base_of_v<std::bidirectional_iterator_tag, iterator_category<T>>;
does the work of what you mentioned so you don't need multiple lines.

one other thing too is that you can do the enable_if in the template parameter in conjunction with an "assignment hack" as well, and it ends up being a bit easier to swallow :v:


C++ code:
// Using __LINE__ and an int here lets us not care about the generic signatures
// as long as only oneis unambiguously available at the end.
// Technically they could both be assigned to 0, but then a compiler error
// wouldn't let us know which culprits did what
template<class Iter, std::enable_if_t<impl::increment_v<Iter>, int> = __LINE__>
void molest(Iter& it)
{
    ++ it;
}

template<class Iter, std::enable_if_t<not impl::increment_v<Iter>, int> = __LINE__>
void molest(Iter& it)
{
    -- it;
}
you can then turn that enable_if into a macro: #define REQUIRE(...) std::enable_if_t<__VA_ARGS__, int> = __LINE__
and now you've got a clean lil' function

C++ code:
#define REQUIRE(...) std::enable_if_t<__VA_ARGS__, int> = __LINE__

namespace impl {
template <class T>
using iterator_category = typename std::iterator_traits<T>::iterator_category;
template <class T>
constexpr bool increment_v = not std::is_base_of_v<std::bidirectional_iterator_tag, iterator_category<T>>;
}

template <class Iter, REQUIRE(not impl::increment_v<Iter>)> void molest (Iter& it) { --it; }
template <class Iter, REQUIRE(impl::increment_v<Iter>)> void molest (Iter& it) { ++it; }

VikingofRock
Aug 24, 2008




hackbunny posted:

C++ code:
void molest(Iter& it, std::enable_if_t<impl::molest_by_increment_v<Iter>, Iter> * = nullptr)
{
	++ it;
}

Maybe I'm missing something obvious (or maybe I missed this part of your post), but why do you switch over to a pointer to an iterator here? And doesn't this increment the pointer to the iterator, not the iterator itself? And in the default case of a null pointer, isn't that undefined behavior?

raminasi
Jan 25, 2005

a last drink with no ice

VikingofRock posted:

Maybe I'm missing something obvious (or maybe I missed this part of your post), but why do you switch over to a pointer to an iterator here? And doesn't this increment the pointer to the iterator, not the iterator itself? And in the default case of a null pointer, isn't that undefined behavior?

where is the pointer to an iterator?

VikingofRock
Aug 24, 2008




raminasi posted:

where is the pointer to an iterator?

Oh yeah actually I misread it. I thought it was

C++ code:
void molest(std::enable_if_t<impl::molest_by_increment_v<Iter>, Iter> *it = nullptr)
{
    ++ it;
}
Nevermind :blush:

Adbot
ADBOT LOVES YOU

VikingofRock
Aug 24, 2008




Actually wait why doesn't this work?

C++ code:
void molest(std::enable_if_t<impl::molest_by_increment_v<Iter>, Iter>& it)
{
    ++ it;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply